@tacuchi/agent-workflow-cli 20.23.0 → 20.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -11
- package/dist/application/context/budget-service.js +128 -0
- package/dist/application/context/budget-service.js.map +1 -0
- package/dist/application/context/bundle-root.js +41 -0
- package/dist/application/context/bundle-root.js.map +1 -0
- package/dist/application/context/manifest.js +150 -0
- package/dist/application/context/manifest.js.map +1 -0
- package/dist/application/context/measure.js +227 -0
- package/dist/application/context/measure.js.map +1 -0
- package/dist/application/context/plan-service.js +177 -0
- package/dist/application/context/plan-service.js.map +1 -0
- package/dist/application/dev-only-services.js +17 -14
- package/dist/application/dev-only-services.js.map +1 -1
- package/dist/application/mcp-host-reader.js +3 -1
- package/dist/application/mcp-host-reader.js.map +1 -1
- package/dist/application/mcp-host-writer.js +86 -41
- package/dist/application/mcp-host-writer.js.map +1 -1
- package/dist/application/plugin-doctor/hooks.js +7 -1
- package/dist/application/plugin-doctor/hooks.js.map +1 -1
- package/dist/application/self/bootstrap.js +30 -16
- package/dist/application/self/bootstrap.js.map +1 -1
- package/dist/application/self/clean-legacy.js +5 -0
- package/dist/application/self/clean-legacy.js.map +1 -1
- package/dist/application/self/detect-hosts.js +31 -45
- package/dist/application/self/detect-hosts.js.map +1 -1
- package/dist/application/self/doctor-self.js +7 -2
- package/dist/application/self/doctor-self.js.map +1 -1
- package/dist/application/self/hooks-toml.js +232 -0
- package/dist/application/self/hooks-toml.js.map +1 -0
- package/dist/application/self/host-states.js +290 -0
- package/dist/application/self/host-states.js.map +1 -0
- package/dist/application/self/install-hooks.js +98 -4
- package/dist/application/self/install-hooks.js.map +1 -1
- package/dist/application/self/install-skill.js +28 -16
- package/dist/application/self/install-skill.js.map +1 -1
- package/dist/application/self/install-targets.js +23 -0
- package/dist/application/self/install-targets.js.map +1 -1
- package/dist/application/self/mcp-config.js +13 -19
- package/dist/application/self/mcp-config.js.map +1 -1
- package/dist/application/self/skills-manager.js +9 -0
- package/dist/application/self/skills-manager.js.map +1 -1
- package/dist/application/self/uninstall.js +162 -26
- package/dist/application/self/uninstall.js.map +1 -1
- package/dist/cli/commands/context-budget.js +79 -0
- package/dist/cli/commands/context-budget.js.map +1 -0
- package/dist/cli/commands/context-plan.js +75 -0
- package/dist/cli/commands/context-plan.js.map +1 -0
- package/dist/cli/commands/dev-only.js +4 -1
- package/dist/cli/commands/dev-only.js.map +1 -1
- package/dist/cli/commands/index.js +4 -0
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/mcp.js +16 -8
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/help-groups.js +4 -0
- package/dist/cli/help-groups.js.map +1 -1
- package/dist/cli/parser.js +4 -0
- package/dist/cli/parser.js.map +1 -1
- package/dist/cli/tui/components/host-admin-section.js +69 -60
- package/dist/cli/tui/components/host-admin-section.js.map +1 -1
- package/dist/cli/tui/data/workflow-content.js +6 -0
- package/dist/cli/tui/data/workflow-content.js.map +1 -1
- package/dist/cli/tui/hosts.js +39 -17
- package/dist/cli/tui/hosts.js.map +1 -1
- package/dist/cli/tui/tabs/config-tab.js +3 -3
- package/dist/cli/tui/tabs/config-tab.js.map +1 -1
- package/dist/cli/tui/tabs/mcp-tab-helpers.js +7 -1
- package/dist/cli/tui/tabs/mcp-tab-helpers.js.map +1 -1
- package/dist/cli/tui/tabs/mcp-tab.js +32 -10
- package/dist/cli/tui/tabs/mcp-tab.js.map +1 -1
- package/dist/cli/tui/tabs/skills-tab.js +13 -4
- package/dist/cli/tui/tabs/skills-tab.js.map +1 -1
- package/dist/cli/tui/tabs/status-tab.js +11 -17
- package/dist/cli/tui/tabs/status-tab.js.map +1 -1
- package/dist/cli/tui/tabs/workflow-tab.js +7 -3
- package/dist/cli/tui/tabs/workflow-tab.js.map +1 -1
- package/dist/cli/tui/tui-prefs.js +7 -1
- package/dist/cli/tui/tui-prefs.js.map +1 -1
- package/dist/domain/harnesses.js +129 -0
- package/dist/domain/harnesses.js.map +1 -1
- package/dist/domain/host-verification.js +20 -0
- package/dist/domain/host-verification.js.map +1 -0
- package/package.json +3 -2
- package/skills/w/SKILL.md +2 -2
- package/skills/w/commands/export-diagrams.md +10 -27
- package/skills/w/commands/export-manuals.md +13 -29
- package/skills/w/commands/export-reports.md +11 -27
- package/skills/w/commands/export-scripts.md +12 -29
- package/skills/w/commands/fix-git.md +21 -32
- package/skills/w/commands/generate-launch.md +13 -32
- package/skills/w/commands/persist.md +8 -29
- package/skills/w/commands/plan-exec.md +14 -30
- package/skills/w/commands/plan-new.md +11 -38
- package/skills/w/commands/plan-refine.md +13 -41
- package/skills/w/commands/quick.md +13 -20
- package/skills/w/commands/resume.md +17 -33
- package/skills/w/commands/spec-new.md +28 -69
- package/skills/w/commands/spec-refine.md +12 -37
- package/skills/w/commands/status.md +12 -19
- package/skills/w/commands/workspace-init.md +13 -20
- package/skills/w/context/MANIFEST.json +486 -0
- package/skills/w/harness/HARNESS.md +24 -17
- package/skills/w/loops/CHASSIS.md +53 -104
- package/skills/w/loops/CODE-POLICIES.md +4 -9
- package/skills/w/loops/plan-exec-loop/LOOP.md +5 -12
- package/skills/w/loops/plan-new-loop/LOOP.md +8 -32
- package/skills/w/loops/plan-refine-loop/LOOP.md +8 -36
- package/skills/w/loops/quick-loop/LOOP.md +4 -10
- package/skills/w/loops/spec-refine-loop/LOOP.md +7 -49
- package/skills/w/modules/ADOPTED-CONTEXT.md +10 -0
- package/skills/w/modules/COMPACTION.md +13 -0
- package/skills/w/modules/DB-RESEARCH-RULE.md +11 -0
- package/skills/w/modules/DB-SCRIPTS-ONLY.md +12 -0
- package/skills/w/modules/EXEC-DB-POLICY.md +7 -0
- package/skills/w/modules/EXEC-PROBE-TASKS.md +11 -0
- package/skills/w/modules/IDEATION-GATE.md +16 -0
- package/skills/w/modules/INCREMENTAL-STRATEGY.md +7 -0
- package/skills/w/modules/LAUNCH-DETECTION.md +26 -0
- package/skills/w/modules/PERSIST-ROUTING.md +16 -0
- package/skills/w/modules/PLAN-DESIGN-SPECS.md +12 -0
- package/skills/w/modules/PLAN-INPUT.md +26 -0
- package/skills/w/modules/PLAN-MODE.md +26 -0
- package/skills/w/modules/PLAN-PROBE-TASKS.md +10 -0
- package/skills/w/modules/PLAN-REFINE-DESIGN-SPECS.md +7 -0
- package/skills/w/modules/PLAN-REFINE-KEYS.md +9 -0
- package/skills/w/modules/PLAN-REFINE-SPLIT.md +12 -0
- package/skills/w/modules/PLAN-SPLIT-GATE.md +14 -0
- package/skills/w/modules/PROBE.md +12 -0
- package/skills/w/modules/PROMPT-CONTINUITY.md +13 -0
- package/skills/w/modules/RECONNAISSANCE.md +48 -0
- package/skills/w/modules/REPLANNING.md +12 -0
- package/skills/w/modules/SESSION-NUMBERING.md +19 -0
- package/skills/w/modules/SIMULATION-LIFECYCLE.md +11 -0
- package/skills/w/modules/SPEC-CHANGE-SHAPE.md +37 -0
- package/skills/w/modules/SPEC-REFINE-KEYS.md +14 -0
- package/skills/w/modules/SPLIT-GATE.md +42 -0
- package/skills/w/modules/WORKSPACE-SCAFFOLD.md +28 -0
- package/skills/w/roles/ui-spec/ROLE.md +1 -1
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ A plan at 100% of checkboxes with zero validated phases is work implemented, not
|
|
|
65
65
|
The published tarball bundles the universal skill set under `skills/w/`. Install it into your host with `--target` (required):
|
|
66
66
|
|
|
67
67
|
```bash
|
|
68
|
-
agent-workflow self install --target claude # or: codex · warp · oz ·
|
|
68
|
+
agent-workflow self install --target claude # or: codex · warp · oz · gemini · opencode · crush · kimi · agents
|
|
69
69
|
agent-workflow self install --target all --confirm-all
|
|
70
70
|
agent-workflow self detect-hosts # which hosts are present + already have it
|
|
71
71
|
agent-workflow self install --target claude --dry-run
|
|
@@ -77,21 +77,44 @@ By default the CLI clears the target host's plugin cache before installing (opt
|
|
|
77
77
|
|
|
78
78
|
`self install --target <host>` installs **SKILL + user-level slash commands + hooks** in one shot, scaled to what the host supports:
|
|
79
79
|
|
|
80
|
-
| Host | SKILL | User-level commands | Hooks |
|
|
81
|
-
|
|
82
|
-
| `claude` | `~/.claude/skills/w/` | `~/.claude/commands/w/<n>.md` → `/w:<n>` | `~/.claude/settings.json` (JSON merge + backup) |
|
|
83
|
-
| `codex` | `~/.codex/skills/w/` | synthesized skills `~/.codex/skills/w-<n>/` → `$w-<n>` (Codex reads no commands dir) |
|
|
84
|
-
| `warp` | `~/.warp/skills/w/` | synthesized skills `~/.warp/skills/w-<n>/` → `/w-<n>` |
|
|
85
|
-
| `
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
88
|
-
| `opencode` | `~/.opencode/skills/w/` | `~/.opencode/command/w/<n>.md` → `/w/<n>` |
|
|
89
|
-
| `crush` | `~/.config/crush/skills/w/` (XDG — the only global root Crush reads; `~/.crush` holds commands only) | `~/.crush/commands/w/<n>.md` → palette `user:w:<n>` |
|
|
80
|
+
| Host | Level | SKILL | User-level commands | Hooks |
|
|
81
|
+
|---|---|---|---|---|
|
|
82
|
+
| `claude` | official | `~/.claude/skills/w/` | `~/.claude/commands/w/<n>.md` → `/w:<n>` | `~/.claude/settings.json` (JSON merge + backup) |
|
|
83
|
+
| `codex` | official | `~/.codex/skills/w/` | synthesized skills `~/.codex/skills/w-<n>/` → `$w-<n>` (Codex reads no commands dir) | not armed (its hooks live in `config.toml`, not wired yet) |
|
|
84
|
+
| `warp` | official | `~/.warp/skills/w/` | synthesized skills `~/.warp/skills/w-<n>/` → `/w-<n>` | none (no hook system) |
|
|
85
|
+
| `gemini` | official | `~/.gemini/skills/w/` | synthesized skills `~/.gemini/skills/w-<n>/` (Antigravity `agy`) + `~/.gemini/commands/w/<n>.toml` → `/w:<n>` (legacy Gemini CLI) | not armed (extension-bundled) |
|
|
86
|
+
| `kimi` | official · pre-1.0 | `~/.kimi-code/skills/w/` (also reads `~/.agents/skills`) | synthesized skills `~/.kimi-code/skills/w-<n>/` → `/skill:w-<n>` | `~/.kimi-code/config.toml` → managed `[[hooks]]` block (marked + backup) |
|
|
87
|
+
| `oz` | best-effort · pre-1.0 | `~/.agents/skills/w/` | synthesized skills `~/.agents/skills/w-<n>/` | none |
|
|
88
|
+
| `opencode` | best-effort | `~/.opencode/skills/w/` | `~/.opencode/command/w/<n>.md` → `/w/<n>` | not armed (JS plugins) |
|
|
89
|
+
| `crush` | best-effort · pre-1.0 | `~/.config/crush/skills/w/` (XDG — the only global root Crush reads; `~/.crush` holds commands only) | `~/.crush/commands/w/<n>.md` → palette `user:w:<n>` | not armed (preliminary) |
|
|
90
|
+
| `agents` | *shared destination, not a host* | `~/.agents/skills/w/` | skipped (shared dir) | skipped |
|
|
90
91
|
|
|
91
92
|
The bundle's internal manuals (`loops/*/LOOP.md`, `roles/*/ROLE.md`, `exports/*/EXPORT.md`, `harness/HARNESS.md`) are deliberately **not** `SKILL.md` files, so hosts that scan skill roots recursively (Codex, OpenCode, Crush) never list them as invocable skills — only the commands and the `w` orientation skill surface. Where a layer is skipped, the SKILL is sufficient — the AI reads it and invokes `agent-workflow <subcommand>` directly.
|
|
92
93
|
|
|
93
94
|
Opt-out flags: `--skill-only`, `--no-commands`, `--no-hooks`. Override the source with `--from /path/to/skills/w`. Other flags: `--confirm-all` (required with `--target all`), `--keep-cache`, `--force`, `--dry-run`.
|
|
94
95
|
|
|
96
|
+
**What `--target all` means.** Every **host** — never the shared skills dirs, which are install destinations rather than hosts and are reached explicitly (`--target agents`). `install` and `uninstall` use the same set, so the round trip matches: what `all` installs is what `all` removes. (`oz` installs into `~/.agents/skills`, so that directory is still covered under `all` through its host.)
|
|
97
|
+
|
|
98
|
+
### Support levels and how long a verification is worth
|
|
99
|
+
|
|
100
|
+
**official** — Claude Code, Codex, Warp, Gemini/Antigravity, Kimi Code. **best-effort** — Oz, OpenCode, Crush. `agents` is a shared destination, not a host, and never counts as one.
|
|
101
|
+
|
|
102
|
+
The difference is what gets *checked*, not what gets installed: `npm run smoke:hosts` probes every host's runtime and version, and for the official ones it additionally installs into a throwaway `HOME` and verifies that the artifacts on disk are the ones this table promises — bundle, command surface, hooks — then uninstalls and verifies they are gone. Its result is written to `src/domain/host-verification.ts`, and that file is the **only** source of a "verified" claim anywhere in the CLI: a host no run has covered is shown as `unverified`, never as verified. `npm run smoke:hosts -- --check` runs the same checks without writing the ledger.
|
|
103
|
+
|
|
104
|
+
Re-verify when a release touches a host. A host marked **pre-1.0** (Kimi Code, Crush, Oz) can change its surface between its own releases faster than we re-check — Kimi Code ships roughly twice a week — so the table states the version a run actually proved and the date it proved it. It is a claim about that version, not a promise about the next one.
|
|
105
|
+
|
|
106
|
+
Validation platform: **macOS** is where all eight runtimes live and where the suite is expected to pass. **Windows** stays best-effort on the existing launch smokes. **Linux** is documented without a guarantee.
|
|
107
|
+
|
|
108
|
+
### Retiring a host
|
|
109
|
+
|
|
110
|
+
Removing a host's key from `InstallTarget` makes TypeScript demand you delete its `TARGET_ROOTS` entry too — and the moment that path is gone, nothing can clean what previous releases installed there. The pattern, modeled on the `crush` root migration:
|
|
111
|
+
|
|
112
|
+
1. **Keep** the target alive in `InstallTarget` / `TARGET_ROOTS` for at least one release, and drop its `HarnessSpec` from `HARNESSES` — it stops being offered as a host while staying reachable for cleanup.
|
|
113
|
+
2. **Move** its old roots into `LEGACY_SKILL_ROOTS_BY_TARGET` so `install`/`uninstall`/`clean-legacy` sweep them, ownership-verified (those roots can be shared namespaces — never delete by dir name alone).
|
|
114
|
+
3. **Then** remove the key, once telemetry or a major version says nobody can still have it installed.
|
|
115
|
+
|
|
116
|
+
Skipping step 2 strands files in a directory no code path can name any more.
|
|
117
|
+
|
|
95
118
|
## TUI
|
|
96
119
|
|
|
97
120
|
Running `agent-workflow` (or `aw`) with no arguments opens the tab-based TUI:
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { resolveBundleRoot } from "./bundle-root.js";
|
|
2
|
+
import { loadManifest } from "./manifest.js";
|
|
3
|
+
import { measureBundle } from "./measure.js";
|
|
4
|
+
export class BaselineError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
constructor(code, message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.name = "BaselineError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** Why the receipt reports no token figure. Single wording for every surface. */
|
|
13
|
+
export const TOKENS_UNAVAILABLE = "ningún host de la matriz expone al agente el consumo real de tokens; el gate usa bytes y no infiere equivalencia";
|
|
14
|
+
export async function runContextBudget(fs, input = {}) {
|
|
15
|
+
const resolved = await resolveBundleRoot(fs, input.root);
|
|
16
|
+
const manifest = await loadManifest(fs, resolved.root);
|
|
17
|
+
const measurement = await measureBundle(fs, resolved.root, resolved.origin, manifest);
|
|
18
|
+
const baseline = await readBaseline(fs, input.baselinePath);
|
|
19
|
+
const budget = baseline === null
|
|
20
|
+
? reportOnly(measurement)
|
|
21
|
+
: deriveBudget(measurement, baseline, manifest.budgetPolicy);
|
|
22
|
+
const offenders = budget.filter((line) => line.ok === false).map(describeOffender);
|
|
23
|
+
return {
|
|
24
|
+
...measurement,
|
|
25
|
+
baseline_path: input.baselinePath ?? null,
|
|
26
|
+
baseline_revision: baseline?.revision ?? null,
|
|
27
|
+
tokens: { available: false, reason: TOKENS_UNAVAILABLE },
|
|
28
|
+
budget,
|
|
29
|
+
offenders,
|
|
30
|
+
verdict: baseline === null ? "measured" : offenders.length === 0 ? "ok" : "over-budget",
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/** The frozen baseline is a prior `aw context-budget` output; only its measurement half is read. */
|
|
34
|
+
export async function readBaseline(fs, path) {
|
|
35
|
+
if (path === undefined)
|
|
36
|
+
return null;
|
|
37
|
+
let raw;
|
|
38
|
+
try {
|
|
39
|
+
raw = await fs.readText(path);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
throw new BaselineError("CONTEXT_BASELINE_MISSING", `no se pudo leer el baseline '${path}'`);
|
|
43
|
+
}
|
|
44
|
+
let parsed;
|
|
45
|
+
try {
|
|
46
|
+
parsed = JSON.parse(raw);
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
throw new BaselineError("CONTEXT_BASELINE_INVALID", `'${path}' no es JSON válido: ${err.message}`);
|
|
50
|
+
}
|
|
51
|
+
// Every field `deriveBudget` dereferences is checked here, so a truncated
|
|
52
|
+
// baseline fails with its own code instead of a TypeError three frames down.
|
|
53
|
+
const missing = ["discovery", "activation", "execution", "guaranteed"].filter((key) => parsed[key] === undefined);
|
|
54
|
+
if (missing.length > 0) {
|
|
55
|
+
throw new BaselineError("CONTEXT_BASELINE_INVALID", `'${path}' no parece un baseline de contexto: falta ${missing.join(", ")}`);
|
|
56
|
+
}
|
|
57
|
+
return parsed;
|
|
58
|
+
}
|
|
59
|
+
function reportOnly(measurement) {
|
|
60
|
+
const lines = [
|
|
61
|
+
{ metric: "discovery", actual: measurement.discovery.bytes },
|
|
62
|
+
{ metric: "activation.median", actual: measurement.activation.median },
|
|
63
|
+
{ metric: "execution.median", actual: measurement.execution.median },
|
|
64
|
+
];
|
|
65
|
+
for (const entry of measurement.activation.entries) {
|
|
66
|
+
lines.push({ metric: `activation.${entry.command}`, actual: entry.bytes });
|
|
67
|
+
}
|
|
68
|
+
for (const journey of measurement.execution.journeys) {
|
|
69
|
+
lines.push({ metric: `journey.${journey.id}`, actual: journey.bytes });
|
|
70
|
+
}
|
|
71
|
+
for (const entry of measurement.guaranteed) {
|
|
72
|
+
lines.push({ metric: `guaranteed.${entry.command}`, actual: entry.bytes });
|
|
73
|
+
}
|
|
74
|
+
lines.push({ metric: "modules.total", actual: measurement.modules.bytes });
|
|
75
|
+
return lines;
|
|
76
|
+
}
|
|
77
|
+
export function deriveBudget(measurement, baseline, policy) {
|
|
78
|
+
const lines = [
|
|
79
|
+
line("discovery", measurement.discovery.bytes, baseline.discovery.bytes, policy.discovery_max_ratio),
|
|
80
|
+
line("activation.median", measurement.activation.median, baseline.activation.median, policy.activation_median_max_ratio),
|
|
81
|
+
line("execution.median", measurement.execution.median, baseline.execution.median, policy.execution_median_max_ratio),
|
|
82
|
+
];
|
|
83
|
+
const baseActivation = new Map(baseline.activation.entries.map((e) => [e.command, e.bytes]));
|
|
84
|
+
for (const entry of measurement.activation.entries) {
|
|
85
|
+
const base = baseActivation.get(entry.command);
|
|
86
|
+
// A command with no baseline row is new since the freeze: it is reported,
|
|
87
|
+
// not judged. Inventing a target for it would be judging it against itself.
|
|
88
|
+
if (base === undefined) {
|
|
89
|
+
lines.push({ metric: `activation.${entry.command}`, actual: entry.bytes });
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
lines.push(line(`activation.${entry.command}`, entry.bytes, base, policy.activation_each_max_ratio));
|
|
93
|
+
}
|
|
94
|
+
const baseJourney = new Map(baseline.execution.journeys.map((j) => [j.id, j.bytes]));
|
|
95
|
+
for (const journey of measurement.execution.journeys) {
|
|
96
|
+
const base = baseJourney.get(journey.id);
|
|
97
|
+
if (base === undefined) {
|
|
98
|
+
lines.push({ metric: `journey.${journey.id}`, actual: journey.bytes });
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
lines.push(line(`journey.${journey.id}`, journey.bytes, base, policy.journey_max_ratio));
|
|
102
|
+
}
|
|
103
|
+
const baseGuaranteed = new Map((baseline.guaranteed ?? []).map((g) => [g.command, g.bytes]));
|
|
104
|
+
for (const entry of measurement.guaranteed) {
|
|
105
|
+
const base = baseGuaranteed.get(entry.command);
|
|
106
|
+
if (base === undefined) {
|
|
107
|
+
lines.push({ metric: `guaranteed.${entry.command}`, actual: entry.bytes });
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
lines.push(line(`guaranteed.${entry.command}`, entry.bytes, base, policy.journey_max_ratio));
|
|
111
|
+
}
|
|
112
|
+
// The conditional tree, budgeted directly. `baseline.modules` is frozen at the
|
|
113
|
+
// revision that introduced it — no pre-split analogue exists, since before the
|
|
114
|
+
// split there were no modules — so this line caps GROWTH, which is the hole it
|
|
115
|
+
// was added to close.
|
|
116
|
+
if (baseline.modules !== undefined) {
|
|
117
|
+
lines.push(line("modules.total", measurement.modules.bytes, baseline.modules.bytes, policy.journey_max_ratio));
|
|
118
|
+
}
|
|
119
|
+
return lines;
|
|
120
|
+
}
|
|
121
|
+
function line(metric, actual, baseline, ratio) {
|
|
122
|
+
const target = Math.floor(baseline * ratio);
|
|
123
|
+
return { metric, actual, baseline, target, ok: actual <= target };
|
|
124
|
+
}
|
|
125
|
+
function describeOffender(entry) {
|
|
126
|
+
return `${entry.metric}: ${entry.actual} B > techo ${entry.target} B (baseline ${entry.baseline} B)`;
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=budget-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget-service.js","sourceRoot":"","sources":["../../../src/application/context/budget-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { resolveBundledSkillPath } from "../self/install-skill.js";
|
|
3
|
+
import { MANIFEST_REL_PATH } from "./manifest.js";
|
|
4
|
+
export class BundleRootError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
constructor(code, message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.name = "BundleRootError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The bundle a context command measures.
|
|
14
|
+
*
|
|
15
|
+
* `--root <path>` points at any tree — a repo checkout for the CI gate, or the
|
|
16
|
+
* tree a host actually installed. With no flag the answer is the PACKAGED
|
|
17
|
+
* bundle, which is the same tree `self install` copies out, so the default is
|
|
18
|
+
* both deterministic and the honest proxy for an installation of this version.
|
|
19
|
+
*
|
|
20
|
+
* Detecting which host is installed where is deliberately NOT done here: that
|
|
21
|
+
* is spec 010's surface, and guessing it would make the receipt's `root` depend
|
|
22
|
+
* on a heuristic instead of on what the caller asked for.
|
|
23
|
+
*/
|
|
24
|
+
export async function resolveBundleRoot(fs, explicitRoot) {
|
|
25
|
+
if (explicitRoot !== undefined) {
|
|
26
|
+
await assertBundle(fs, explicitRoot, "explicit");
|
|
27
|
+
return { root: explicitRoot, origin: "explicit" };
|
|
28
|
+
}
|
|
29
|
+
const packaged = await resolveBundledSkillPath();
|
|
30
|
+
if (packaged === null) {
|
|
31
|
+
throw new BundleRootError("CONTEXT_ROOT_NOT_FOUND", "No se encontró el bundle empaquetado junto a la CLI. Pasá --root <path> con el árbol a medir.");
|
|
32
|
+
}
|
|
33
|
+
await assertBundle(fs, packaged, "packaged");
|
|
34
|
+
return { root: packaged, origin: "packaged" };
|
|
35
|
+
}
|
|
36
|
+
async function assertBundle(fs, root, origin) {
|
|
37
|
+
if (!(await fs.exists(join(root, MANIFEST_REL_PATH)))) {
|
|
38
|
+
throw new BundleRootError("CONTEXT_ROOT_NOT_A_BUNDLE", `'${root}' (${origin}) no trae ${MANIFEST_REL_PATH}: no es un bundle w medible`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=bundle-root.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundle-root.js","sourceRoot":"","sources":["../../../src/application/context/bundle-root.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAUlD,MAAM,OAAO,eAAgB,SAAQ,KAAK;IAE7B;IADX,YACW,IAAY,EACrB,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;QAHN,SAAI,GAAJ,IAAI,CAAQ;QAIrB,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,EAAkB,EAClB,YAAqB;IAErB,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QAC/B,MAAM,YAAY,CAAC,EAAE,EAAE,YAAY,EAAE,UAAU,CAAC,CAAC;QACjD,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IACpD,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,uBAAuB,EAAE,CAAC;IACjD,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACtB,MAAM,IAAI,eAAe,CACvB,wBAAwB,EACxB,+FAA+F,CAChG,CAAC;IACJ,CAAC;IACD,MAAM,YAAY,CAAC,EAAE,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;IAC7C,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;AAChD,CAAC;AAED,KAAK,UAAU,YAAY,CACzB,EAAkB,EAClB,IAAY,EACZ,MAAwB;IAExB,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAAE,CAAC;QACtD,MAAM,IAAI,eAAe,CACvB,2BAA2B,EAC3B,IAAI,IAAI,MAAM,MAAM,aAAa,iBAAiB,6BAA6B,CAChF,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
export const MANIFEST_REL_PATH = join("context", "MANIFEST.json");
|
|
3
|
+
/** Supported manifest schema. A newer bundle is a load error, never a guess. */
|
|
4
|
+
const SUPPORTED_VERSION = 1;
|
|
5
|
+
const POLICY_KEYS = [
|
|
6
|
+
"discovery_max_ratio",
|
|
7
|
+
"activation_median_max_ratio",
|
|
8
|
+
"activation_each_max_ratio",
|
|
9
|
+
"execution_median_max_ratio",
|
|
10
|
+
"journey_max_ratio",
|
|
11
|
+
];
|
|
12
|
+
export class ManifestError extends Error {
|
|
13
|
+
code;
|
|
14
|
+
constructor(code, message) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.code = code;
|
|
17
|
+
this.name = "ManifestError";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export async function loadManifest(fs, bundleRoot) {
|
|
21
|
+
const path = join(bundleRoot, MANIFEST_REL_PATH);
|
|
22
|
+
if (!(await fs.exists(path))) {
|
|
23
|
+
throw new ManifestError("CONTEXT_MANIFEST_MISSING", `El bundle en '${bundleRoot}' no trae ${MANIFEST_REL_PATH}: no describe su propio grafo de contexto`);
|
|
24
|
+
}
|
|
25
|
+
let raw;
|
|
26
|
+
try {
|
|
27
|
+
raw = JSON.parse(await fs.readText(path));
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
throw new ManifestError("CONTEXT_MANIFEST_INVALID", `${MANIFEST_REL_PATH} no es JSON válido: ${err.message}`);
|
|
31
|
+
}
|
|
32
|
+
return parseManifest(raw);
|
|
33
|
+
}
|
|
34
|
+
/** Exported for tests: the same validation, over an in-memory object. */
|
|
35
|
+
export function parseManifest(raw) {
|
|
36
|
+
const root = asRecord(raw, "raíz del manifiesto");
|
|
37
|
+
const version = root.version;
|
|
38
|
+
if (version !== SUPPORTED_VERSION) {
|
|
39
|
+
throw new ManifestError("CONTEXT_MANIFEST_VERSION", `versión de manifiesto no soportada: ${String(version)} (esta CLI lee ${SUPPORTED_VERSION})`);
|
|
40
|
+
}
|
|
41
|
+
const signals = readSignals(root.signals);
|
|
42
|
+
const commands = readCommands(root.commands, signals);
|
|
43
|
+
const journeys = readJourneys(root.journeys, commands, signals);
|
|
44
|
+
const budgetPolicy = readPolicy(root.budget_policy);
|
|
45
|
+
return { version, signals, commands, journeys, budgetPolicy };
|
|
46
|
+
}
|
|
47
|
+
function readSignals(raw) {
|
|
48
|
+
const record = asRecord(raw, "signals");
|
|
49
|
+
const out = {};
|
|
50
|
+
for (const [id, description] of Object.entries(record)) {
|
|
51
|
+
if (id.startsWith("$"))
|
|
52
|
+
continue;
|
|
53
|
+
if (typeof description !== "string" || description.length === 0) {
|
|
54
|
+
throw new ManifestError("CONTEXT_MANIFEST_INVALID", `signal '${id}' sin descripción`);
|
|
55
|
+
}
|
|
56
|
+
out[id] = description;
|
|
57
|
+
}
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
function readCommands(raw, signals) {
|
|
61
|
+
const record = asRecord(raw, "commands");
|
|
62
|
+
const out = {};
|
|
63
|
+
for (const [name, value] of Object.entries(record)) {
|
|
64
|
+
if (name.startsWith("$"))
|
|
65
|
+
continue;
|
|
66
|
+
const entry = asRecord(value, `commands.${name}`);
|
|
67
|
+
const core = asStringArray(entry.core, `commands.${name}.core`);
|
|
68
|
+
if (core.length === 0) {
|
|
69
|
+
throw new ManifestError("CONTEXT_MANIFEST_INVALID", `commands.${name}.core vacío: todo comando carga al menos su propio cuerpo`);
|
|
70
|
+
}
|
|
71
|
+
out[name] = { core, modules: readModules(entry.modules, name, signals) };
|
|
72
|
+
}
|
|
73
|
+
if (Object.keys(out).length === 0) {
|
|
74
|
+
throw new ManifestError("CONTEXT_MANIFEST_INVALID", "commands vacío");
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
function readModules(raw, command, signals) {
|
|
79
|
+
if (!Array.isArray(raw)) {
|
|
80
|
+
throw new ManifestError("CONTEXT_MANIFEST_INVALID", `commands.${command}.modules debe ser una lista`);
|
|
81
|
+
}
|
|
82
|
+
return raw.map((item, index) => {
|
|
83
|
+
const module = asRecord(item, `commands.${command}.modules[${index}]`);
|
|
84
|
+
const path = module.path;
|
|
85
|
+
const signal = module.signal;
|
|
86
|
+
if (typeof path !== "string" || path.length === 0) {
|
|
87
|
+
throw new ManifestError("CONTEXT_MANIFEST_INVALID", `commands.${command}.modules[${index}].path ausente`);
|
|
88
|
+
}
|
|
89
|
+
if (typeof signal !== "string" || !(signal in signals)) {
|
|
90
|
+
throw new ManifestError("CONTEXT_MANIFEST_INVALID", `commands.${command}.modules[${index}].signal '${String(signal)}' no está declarada en signals`);
|
|
91
|
+
}
|
|
92
|
+
const requires = module.requires;
|
|
93
|
+
if (requires !== undefined && (typeof requires !== "string" || requires.length === 0)) {
|
|
94
|
+
throw new ManifestError("CONTEXT_MANIFEST_INVALID", `commands.${command}.modules[${index}].requires debe nombrar una capacidad`);
|
|
95
|
+
}
|
|
96
|
+
return requires === undefined ? { path, signal } : { path, signal, requires };
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
function readJourneys(raw, commands, signals) {
|
|
100
|
+
if (!Array.isArray(raw) || raw.length === 0) {
|
|
101
|
+
throw new ManifestError("CONTEXT_MANIFEST_INVALID", "journeys debe ser una lista no vacía");
|
|
102
|
+
}
|
|
103
|
+
return raw.map((item, index) => {
|
|
104
|
+
const journey = asRecord(item, `journeys[${index}]`);
|
|
105
|
+
const id = asNonEmptyString(journey.id, `journeys[${index}].id`);
|
|
106
|
+
const label = asNonEmptyString(journey.label, `journeys[${index}].label`);
|
|
107
|
+
const command = asNonEmptyString(journey.command, `journeys[${index}].command`);
|
|
108
|
+
if (!(command in commands)) {
|
|
109
|
+
throw new ManifestError("CONTEXT_MANIFEST_INVALID", `journeys[${index}].command '${command}' no existe en commands`);
|
|
110
|
+
}
|
|
111
|
+
const journeySignals = asStringArray(journey.signals, `journeys[${index}].signals`);
|
|
112
|
+
for (const signal of journeySignals) {
|
|
113
|
+
if (!(signal in signals)) {
|
|
114
|
+
throw new ManifestError("CONTEXT_MANIFEST_INVALID", `journeys[${index}].signals contiene '${signal}', no declarada en signals`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return { id, label, command, signals: journeySignals };
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
function readPolicy(raw) {
|
|
121
|
+
const record = asRecord(raw, "budget_policy");
|
|
122
|
+
const out = {};
|
|
123
|
+
for (const key of POLICY_KEYS) {
|
|
124
|
+
const value = record[key];
|
|
125
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
126
|
+
throw new ManifestError("CONTEXT_MANIFEST_INVALID", `budget_policy.${key} debe ser un número positivo`);
|
|
127
|
+
}
|
|
128
|
+
out[key] = value;
|
|
129
|
+
}
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
function asRecord(value, where) {
|
|
133
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
134
|
+
throw new ManifestError("CONTEXT_MANIFEST_INVALID", `${where} debe ser un objeto`);
|
|
135
|
+
}
|
|
136
|
+
return value;
|
|
137
|
+
}
|
|
138
|
+
function asStringArray(value, where) {
|
|
139
|
+
if (!Array.isArray(value)) {
|
|
140
|
+
throw new ManifestError("CONTEXT_MANIFEST_INVALID", `${where} debe ser una lista`);
|
|
141
|
+
}
|
|
142
|
+
return value.map((item, index) => asNonEmptyString(item, `${where}[${index}]`));
|
|
143
|
+
}
|
|
144
|
+
function asNonEmptyString(value, where) {
|
|
145
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
146
|
+
throw new ManifestError("CONTEXT_MANIFEST_INVALID", `${where} debe ser un string no vacío`);
|
|
147
|
+
}
|
|
148
|
+
return value;
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../../src/application/context/manifest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { readPackageVersion } from "../../runtime/version.js";
|
|
4
|
+
import { splitCommandDoc } from "../self/install-skill.js";
|
|
5
|
+
/**
|
|
6
|
+
* The three tramos of context cost (spec 009 § Requirement).
|
|
7
|
+
*
|
|
8
|
+
* Everything here is deterministic and offline: bytes of UTF-8 content read
|
|
9
|
+
* from a tree on disk, nothing sampled, nothing asked of a host. That is what
|
|
10
|
+
* lets a gate compare two revisions and get an answer that means something.
|
|
11
|
+
*
|
|
12
|
+
* Bytes — not tokens — is the gate metric on purpose: no host in the harness
|
|
13
|
+
* matrix exposes real token accounting to the agent, and inventing a bytes↔
|
|
14
|
+
* tokens equivalence would make the budget unfalsifiable (spec 009, scenario
|
|
15
|
+
* "metricas de tokens no disponibles").
|
|
16
|
+
*/
|
|
17
|
+
/** The file the discovery surface always includes besides the commands. */
|
|
18
|
+
const ORIENTATION_DOC = "SKILL.md";
|
|
19
|
+
const COMMANDS_DIR = "commands";
|
|
20
|
+
/** Not a command: the commands folder's own index. */
|
|
21
|
+
const COMMANDS_INDEX = "README.md";
|
|
22
|
+
export function byteLength(text) {
|
|
23
|
+
return Buffer.byteLength(text, "utf8");
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Median of a byte series, floored.
|
|
27
|
+
*
|
|
28
|
+
* Floored, and on the mean of the two central values for an even count, because
|
|
29
|
+
* a budget derived from it has to be reproducible to the byte across machines —
|
|
30
|
+
* a rounding rule chosen per call site is how two "same" measurements start
|
|
31
|
+
* disagreeing.
|
|
32
|
+
*/
|
|
33
|
+
export function median(values) {
|
|
34
|
+
if (values.length === 0)
|
|
35
|
+
return 0;
|
|
36
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
37
|
+
const mid = sorted.length >> 1;
|
|
38
|
+
if (sorted.length % 2 === 1)
|
|
39
|
+
return sorted[mid] ?? 0;
|
|
40
|
+
return Math.floor(((sorted[mid - 1] ?? 0) + (sorted[mid] ?? 0)) / 2);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Discovery: the metadata a host renders BEFORE the command is invoked — the
|
|
44
|
+
* orientation skill's description plus one per command. Derived from the tree
|
|
45
|
+
* rather than declared in the manifest: a command that ships without paying
|
|
46
|
+
* discovery is precisely the drift the aggregate budget exists to catch.
|
|
47
|
+
*/
|
|
48
|
+
export async function measureDiscovery(fs, root) {
|
|
49
|
+
const paths = [ORIENTATION_DOC, ...(await listCommandDocs(fs, root))];
|
|
50
|
+
const entries = [];
|
|
51
|
+
for (const path of paths) {
|
|
52
|
+
entries.push({ path, bytes: byteLength(await readDescription(fs, join(root, path))) });
|
|
53
|
+
}
|
|
54
|
+
return { bytes: entries.reduce((sum, e) => sum + e.bytes, 0), entries };
|
|
55
|
+
}
|
|
56
|
+
/** Activation: the command body itself — what the host loads the moment it is invoked. */
|
|
57
|
+
export async function measureActivation(fs, root) {
|
|
58
|
+
const entries = [];
|
|
59
|
+
for (const path of await listCommandDocs(fs, root)) {
|
|
60
|
+
const command = path.slice(`${COMMANDS_DIR}/`.length, -".md".length);
|
|
61
|
+
entries.push({ command, bytes: await fileBytes(fs, join(root, path)) });
|
|
62
|
+
}
|
|
63
|
+
entries.sort((a, b) => a.command.localeCompare(b.command));
|
|
64
|
+
return { median: median(entries.map((e) => e.bytes)), entries };
|
|
65
|
+
}
|
|
66
|
+
/** Execution: the whole read-set of a journey, core plus the modules its signals activate. */
|
|
67
|
+
export async function measureExecution(fs, root, manifest) {
|
|
68
|
+
const journeys = [];
|
|
69
|
+
for (const journey of manifest.journeys) {
|
|
70
|
+
const readSet = resolveReadSet(manifest, journey.command, journey.signals);
|
|
71
|
+
const files = [];
|
|
72
|
+
for (const path of readSet.paths) {
|
|
73
|
+
const full = join(root, path);
|
|
74
|
+
const exists = await fs.exists(full);
|
|
75
|
+
files.push({
|
|
76
|
+
path,
|
|
77
|
+
bytes: exists ? await fileBytes(fs, full) : 0,
|
|
78
|
+
missing: !exists,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
const entry = manifest.commands[journey.command];
|
|
82
|
+
const everyPath = [...(entry?.core ?? []), ...(entry?.modules ?? []).map((m) => m.path)];
|
|
83
|
+
let safeBytes = 0;
|
|
84
|
+
for (const path of everyPath) {
|
|
85
|
+
const full = join(root, path);
|
|
86
|
+
if (await fs.exists(full))
|
|
87
|
+
safeBytes += await fileBytes(fs, full);
|
|
88
|
+
}
|
|
89
|
+
journeys.push({
|
|
90
|
+
id: journey.id,
|
|
91
|
+
label: journey.label,
|
|
92
|
+
command: journey.command,
|
|
93
|
+
signals: journey.signals,
|
|
94
|
+
bytes: files.reduce((sum, f) => sum + f.bytes, 0),
|
|
95
|
+
files,
|
|
96
|
+
safe_bytes: safeBytes,
|
|
97
|
+
degraded: readSet.degraded || files.some((f) => f.missing),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return { median: median(journeys.map((j) => j.bytes)), journeys };
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* From (command, observed signals) to the ordered list of documents to read.
|
|
104
|
+
*
|
|
105
|
+
* Order is core-then-modules, and it is the order the agent reads in: the core
|
|
106
|
+
* carries the hard floor, so it can never end up behind a conditional module.
|
|
107
|
+
*
|
|
108
|
+
* An unknown SIGNAL does not fail: a caller that knows a signal this bundle
|
|
109
|
+
* does not is a half-finished update, which is a normal state, and the safe
|
|
110
|
+
* answer there is MORE context — every module, marked degraded.
|
|
111
|
+
*
|
|
112
|
+
* An unknown COMMAND is different and returns an EMPTY set: there is no core to
|
|
113
|
+
* fall back to, so nothing can be resolved. `runContextPlan` turns that into a
|
|
114
|
+
* hard error naming the commands that do exist.
|
|
115
|
+
*/
|
|
116
|
+
export function resolveReadSet(manifest, command, signals) {
|
|
117
|
+
const entry = manifest.commands[command];
|
|
118
|
+
if (entry === undefined) {
|
|
119
|
+
return {
|
|
120
|
+
paths: [],
|
|
121
|
+
degraded: true,
|
|
122
|
+
reasons: [`comando '${command}' no está declarado en el manifiesto`],
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
const reasons = [];
|
|
126
|
+
const unknownSignals = signals.filter((s) => !(s in manifest.signals));
|
|
127
|
+
if (unknownSignals.length > 0) {
|
|
128
|
+
reasons.push(`señales no declaradas: ${unknownSignals.join(", ")}`);
|
|
129
|
+
}
|
|
130
|
+
const observed = new Set(signals);
|
|
131
|
+
// An unknown signal means the caller knows something this bundle does not;
|
|
132
|
+
// the safe reading of that is every module, not the ones we happened to match.
|
|
133
|
+
const takeAll = unknownSignals.length > 0;
|
|
134
|
+
const modules = entry.modules.filter((m) => takeAll || observed.has(m.signal));
|
|
135
|
+
const paths = [];
|
|
136
|
+
for (const path of [...entry.core, ...modules.map((m) => m.path)]) {
|
|
137
|
+
if (!paths.includes(path))
|
|
138
|
+
paths.push(path);
|
|
139
|
+
}
|
|
140
|
+
return { paths, degraded: reasons.length > 0, reasons };
|
|
141
|
+
}
|
|
142
|
+
export async function measureTree(fs, root) {
|
|
143
|
+
const files = await listAllFiles(fs, root, "");
|
|
144
|
+
files.sort();
|
|
145
|
+
const hash = createHash("sha256");
|
|
146
|
+
for (const rel of files) {
|
|
147
|
+
hash.update(`${rel}:${await fileBytes(fs, join(root, rel))}\n`);
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
cli_version: readPackageVersion(),
|
|
151
|
+
content_digest: hash.digest("hex").slice(0, 16),
|
|
152
|
+
file_count: files.length,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/** Core read-set cost of every command the manifest declares, no signal observed. */
|
|
156
|
+
export async function measureGuaranteed(fs, root, manifest) {
|
|
157
|
+
const out = [];
|
|
158
|
+
for (const command of Object.keys(manifest.commands).sort()) {
|
|
159
|
+
const files = resolveReadSet(manifest, command, []).paths;
|
|
160
|
+
let bytes = 0;
|
|
161
|
+
for (const rel of files) {
|
|
162
|
+
const full = join(root, rel);
|
|
163
|
+
if (await fs.exists(full))
|
|
164
|
+
bytes += await fileBytes(fs, full);
|
|
165
|
+
}
|
|
166
|
+
out.push({ command, bytes, files });
|
|
167
|
+
}
|
|
168
|
+
return out;
|
|
169
|
+
}
|
|
170
|
+
/** Every distinct module the manifest declares, counted once. */
|
|
171
|
+
export async function measureModules(fs, root, manifest) {
|
|
172
|
+
const seen = new Set();
|
|
173
|
+
for (const entry of Object.values(manifest.commands)) {
|
|
174
|
+
for (const module of entry.modules)
|
|
175
|
+
seen.add(module.path);
|
|
176
|
+
}
|
|
177
|
+
let bytes = 0;
|
|
178
|
+
for (const rel of seen) {
|
|
179
|
+
const full = join(root, rel);
|
|
180
|
+
if (await fs.exists(full))
|
|
181
|
+
bytes += await fileBytes(fs, full);
|
|
182
|
+
}
|
|
183
|
+
return { count: seen.size, bytes };
|
|
184
|
+
}
|
|
185
|
+
export async function measureBundle(fs, root, rootOrigin, manifest) {
|
|
186
|
+
return {
|
|
187
|
+
root,
|
|
188
|
+
root_origin: rootOrigin,
|
|
189
|
+
revision: await measureTree(fs, root),
|
|
190
|
+
discovery: await measureDiscovery(fs, root),
|
|
191
|
+
activation: await measureActivation(fs, root),
|
|
192
|
+
execution: await measureExecution(fs, root, manifest),
|
|
193
|
+
guaranteed: await measureGuaranteed(fs, root, manifest),
|
|
194
|
+
modules: await measureModules(fs, root, manifest),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
async function listCommandDocs(fs, root) {
|
|
198
|
+
const dir = join(root, COMMANDS_DIR);
|
|
199
|
+
const entries = await fs.list(dir);
|
|
200
|
+
return entries
|
|
201
|
+
.filter((e) => e.type === "file" && e.name.endsWith(".md") && e.name !== COMMANDS_INDEX)
|
|
202
|
+
.map((e) => `${COMMANDS_DIR}/${e.name}`)
|
|
203
|
+
.sort();
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* The description as the INSTALLER extracts it — `splitCommandDoc`, the single
|
|
207
|
+
* parser every host wrapper already depends on. Measuring discovery with a
|
|
208
|
+
* second parser of its own would budget a string no host ever renders.
|
|
209
|
+
*/
|
|
210
|
+
async function readDescription(fs, path) {
|
|
211
|
+
return splitCommandDoc(await fs.readText(path)).description ?? "";
|
|
212
|
+
}
|
|
213
|
+
async function fileBytes(fs, path) {
|
|
214
|
+
return byteLength(await fs.readText(path));
|
|
215
|
+
}
|
|
216
|
+
async function listAllFiles(fs, root, prefix) {
|
|
217
|
+
const out = [];
|
|
218
|
+
for (const entry of await fs.list(join(root, prefix))) {
|
|
219
|
+
const rel = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
220
|
+
if (entry.type === "dir")
|
|
221
|
+
out.push(...(await listAllFiles(fs, root, rel)));
|
|
222
|
+
else if (entry.type === "file")
|
|
223
|
+
out.push(rel);
|
|
224
|
+
}
|
|
225
|
+
return out;
|
|
226
|
+
}
|
|
227
|
+
//# sourceMappingURL=measure.js.map
|