Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@ Pre-1.0 releases followed it in spirit; their breaking changes are marked **Brea

### Added

- **`describePlan(plan)` turns a compiled plan into JSON.** The framework
already knows exactly what an extension registers — that is what compiling
declarations before running them is for — but an `ApplicationPlan` holds
factories, handlers and token objects, so the answer was locked inside it.
The description carries module ids, service tokens and the edges between
them, command ids and titles, settings keys and defaults, watcher globs and
view ids, and nothing callable.

It is deterministic and in declaration order, so the output is worth
committing: a diff means a declaration changed. That is the review question
`git diff` on a large module rarely answers directly, and it is the same
document a manifest cross-check or a dependency diagram wants.

- **A shutdown that runs out of budget now says what was holding it.** The
`application.shutdownTimeout` diagnostic carried a phase name and nothing
else, which left the only question that matters unanswered: which hosted
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ has stopped being obvious._

## Features

- **Declare, Then Run**: Commands, services, settings, storage, secrets, watchers and views are data; compiling them produces an immutable plan
- **Declare, Then Run**: Commands, services, settings, storage, secrets, watchers and views are data; compiling them produces an immutable plan — `describePlan` hands you that plan as JSON, to diff in a review or feed to a tool
- **Preflight Before VS Code**: Duplicate ids, a missing service, a dependency cycle, a captive dependency — all rejected at import time, before a single API call
- **One Cleanup Owner**: `deactivate` is the only teardown path; `context.subscriptions` gets one synchronous failsafe and nothing else
- **Every Unit of Work Is an Operation**: A command invocation or a watcher batch arrives with an id, a logger, a combined `AbortSignal`, a progress session and a resource scope
Expand Down
53 changes: 53 additions & 0 deletions docs/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,59 @@ every extension's deactivation against a few seconds and then exits; the
framework's own budget sits inside that, and past it pending work is abandoned.
Knowing _which_ work is the difference between a mystery and a fix.

### The plan, as data

Diagnostics say what is happening; `describePlan` says what was declared. It
turns a compiled plan into JSON — module ids, service tokens and the edges
between them, command ids and titles, settings keys and defaults, watcher
globs, view ids — with nothing callable in it.

<!-- sample: docs/samples/describe-plan.ts -->

```ts
import { describePlan } from '@kkdev92/vscode-ext-kit';

import { app } from './extension.js';

/**
* What this extension registers, as JSON.
*
* Commit the output and a pull request shows the topology change beside the
* code change: a new command, a service that gained a dependency, a watcher
* whose glob moved. Deterministic, so a diff means a declaration changed.
*/
export function planAsJson(): string {
return JSON.stringify(describePlan(app.plan), null, 2);
}

/** Every command in the plan, with the module that declared it. */
export function commandOwners(): readonly string[] {
return describePlan(app.plan).commands.map((command) => `${command.id} (${command.moduleId})`);
}

/**
* The service graph as edges, which is most of what a dependency diagram is.
*
* Token ids, not token objects: the description carries nothing callable, so
* there is nothing here to resolve or mutate.
*/
export function serviceEdges(): readonly string[] {
return describePlan(app.plan).services.flatMap((service) =>
Object.values(service.dependencies).map((dependency) => `${service.token} -> ${dependency}`)
);
}
```

It is deterministic and in declaration order, so the output is worth
committing: a diff in the file means a declaration changed, which is the
review question `git diff` on a large module rarely answers directly. The same
document is what a manifest cross-check and a dependency diagram want, and it
is the honest answer to "what does this extension actually register?" for
anyone — or anything — reading the codebase for the first time.

Secret _keys_ appear, because a declared key is metadata the source already
states in the clear. Secret values do not exist at plan time.

## Keeping package.json honest

VS Code reads the manifest before any extension code runs, so `src` and
Expand Down
31 changes: 31 additions & 0 deletions docs/samples/describe-plan.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { describePlan } from '@kkdev92/vscode-ext-kit';

import { app } from './extension.js';

/**
* What this extension registers, as JSON.
*
* Commit the output and a pull request shows the topology change beside the
* code change: a new command, a service that gained a dependency, a watcher
* whose glob moved. Deterministic, so a diff means a declaration changed.
*/
export function planAsJson(): string {
return JSON.stringify(describePlan(app.plan), null, 2);
}

/** Every command in the plan, with the module that declared it. */
export function commandOwners(): readonly string[] {
return describePlan(app.plan).commands.map((command) => `${command.id} (${command.moduleId})`);
}

/**
* The service graph as edges, which is most of what a dependency diagram is.
*
* Token ids, not token objects: the description carries nothing callable, so
* there is nothing here to resolve or mutate.
*/
export function serviceEdges(): readonly string[] {
return describePlan(app.plan).services.flatMap((service) =>
Object.values(service.dependencies).map((dependency) => `${service.token} -> ${dependency}`)
);
}
Loading