@osovv/vv-opencode 1.3.6-rc.6 → 1.3.6-rc.8

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.
Files changed (38) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +32 -2
  3. package/dist/cli.js +5 -3
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/lint.d.ts +74 -0
  6. package/dist/commands/lint.js +269 -0
  7. package/dist/commands/lint.js.map +1 -0
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.js +6 -5
  10. package/dist/index.js.map +1 -1
  11. package/dist/lib/opencode.js +3 -2
  12. package/dist/lib/opencode.js.map +1 -1
  13. package/dist/lib/plugin-toggle-config.d.ts +13 -1
  14. package/dist/lib/plugin-toggle-config.js +38 -4
  15. package/dist/lib/plugin-toggle-config.js.map +1 -1
  16. package/dist/lib/spec-lint-cache.d.ts +34 -0
  17. package/dist/lib/spec-lint-cache.js +140 -0
  18. package/dist/lib/spec-lint-cache.js.map +1 -0
  19. package/dist/lib/spec-lint.d.ts +57 -0
  20. package/dist/lib/spec-lint.js +963 -0
  21. package/dist/lib/spec-lint.js.map +1 -0
  22. package/dist/lib/vvoc-config.d.ts +25 -0
  23. package/dist/lib/vvoc-config.js +9 -0
  24. package/dist/lib/vvoc-config.js.map +1 -1
  25. package/dist/lib/vvoc-paths.d.ts +1 -0
  26. package/dist/lib/vvoc-paths.js +15 -5
  27. package/dist/lib/vvoc-paths.js.map +1 -1
  28. package/dist/plugins/spec-guard/index.d.ts +39 -0
  29. package/dist/plugins/spec-guard/index.js +253 -0
  30. package/dist/plugins/spec-guard/index.js.map +1 -0
  31. package/package.json +6 -2
  32. package/schemas/vvoc/v3.json +23 -6
  33. package/templates/skills/vv-execute/SKILL.md +8 -3
  34. package/templates/skills/vv-plan/SKILL.md +7 -7
  35. package/templates/skills/vv-plan/references/plan-template.xml +11 -6
  36. package/templates/skills/vv-spec/SKILL.md +5 -3
  37. package/templates/skills/vv-spec/references/design-context-template.xml +2 -2
  38. package/templates/skills/vv-spec/references/spec-template.xml +17 -9
package/CHANGELOG.md CHANGED
@@ -1,3 +1,20 @@
1
+ ## <small>1.3.6-rc.8 (2026-08-29)</small>
2
+
3
+ ### Summary
4
+
5
+ This release fixes a reliability issue in the spec-guard plugin and corrects the documented GRACE CLI workflow. Read-path validation previously linted the tool's rendered output, which hosts may decorate with envelope tags and line-number prefixes, causing false well-formedness errors on valid spec files; read verdicts are now derived from the actual artifact file on disk, so validation results are accurate and independent of host rendering. In addition, the agent documentation now directs users to invoke the installed stable `grace` binary directly rather than the stale `rc` dist-tag, ensuring linting and status checks run against the correct validator version.
6
+
7
+ * docs(agents): invoke the installed grace binary instead of the stale rc tag ([8e59a00](https://github.com/osovv/vv-opencode/commit/8e59a00))
8
+ * fix(spec-guard): derive read verdicts from the artifact file, not the tool rendering ([ea8bd52](https://github.com/osovv/vv-opencode/commit/ea8bd52))
9
+
10
+ ## <small>1.3.6-rc.7 (2026-08-29)</small>
11
+
12
+ ### Summary
13
+
14
+ This release introduces deterministic, host-side verification for spec-package artifacts with a new COMPONENT identity format: spec, plan, and design-context components now share element-name identities across documents, fields are unified to snake_case, and the plan-subset-of-spec traceability rule is enforced mechanically rather than left to convention. A new strict lint engine checks well-formedness, template contracts, identity uniqueness, references, and lifecycle-aware severity, with results cached content-addressed under the XDG cache home. Users get a new `vvoc lint` command that targets files, packages, or whole specs trees with stable exit codes plus strict, JSON, no-cache, and archive flags (archived packages are skipped by default), and a new SpecGuardPlugin that appends lint verdicts to reads of active artifacts and validates writes through the same cache, defaulting to warn mode while enforce only refuses ERROR-severity writes so in-progress draft composition is never blocked and internal failures degrade fail-open. Schema v3, plugin toggles, exports, templates, skills, and README stay synchronized with the new format and command.
15
+
16
+ * feat(spec-lint): add COMPONENT identity format, vvoc lint, and spec-guard ([a28cde6](https://github.com/osovv/vv-opencode/commit/a28cde6))
17
+
1
18
  ## <small>1.3.6-rc.6 (2026-08-27)</small>
2
19
 
3
20
  ### Summary
package/README.md CHANGED
@@ -146,12 +146,13 @@ All artifacts for one feature live together:
146
146
 
147
147
  Package ids are date-prefixed (`YYYY-MM-DD-<slug>`, for example `2026-06-24-cache-store`) so active packages sort by creation date; the prefix is date-only, never a full timestamp. Spec and plan lifecycle runs through a top-level status: `draft` while being written, `approved` after explicit user approval, `applied` after successful execution. `vv-execute` archives applied packages by moving the whole directory to `.vvoc/specs/archive/YYYY-MM-DD-<slug>-<timestamp>/`.
148
148
 
149
- Specs and plans are XML, so requirements, tasks, acceptance criteria, and dependencies stay grep-able. Task and wave identity lives in unique element names (`<TASK-T-001>…</TASK-T-001>`, `<WAVE-1>…</WAVE-1>`), so grep/sed extraction stays exact without a separate query language:
149
+ Specs and plans are XML, so requirements, tasks, acceptance criteria, and dependencies stay grep-able. Identity lives in unique element names — tasks and waves as `<TASK-T-001>…</TASK-T-001>` and `<WAVE-1>…</WAVE-1>`, spec and plan components as `<COMPONENT-CACHE-STORE>…</COMPONENT-CACHE-STORE>` — so grep/sed extraction stays exact without a separate query language. Field names use snake_case; component references are bare slugs; plan architecture components always reuse the exact identities declared in spec.xml, so a plan component without a spec component is a lint error, not a convention breach:
150
150
 
151
151
  ```bash
152
152
  grep '<TASK-T-' .vvoc/specs/*/plan.xml # task ids
153
153
  grep '<criterion>' .vvoc/specs/*/plan.xml # acceptance criteria
154
154
  grep '<task_id>' .vvoc/specs/*/plan.xml # dependency graph
155
+ grep '<COMPONENT-' .vvoc/specs/*/*.xml # component map across spec and plan
155
156
  ```
156
157
 
157
158
  `vv-controller` explicitly routes `vv-spec`, `vv-plan`, and `vv-review`; `vv-execute`, `vv-reflect`, and `vv-handoff` are available as managed skills for plan execution, durable repository memory, and end-of-session handoff notes.
@@ -160,7 +161,7 @@ grep '<task_id>' .vvoc/specs/*/plan.xml # dependency graph
160
161
 
161
162
  ## What's inside
162
163
 
163
- ### The eleven plugins
164
+ ### The twelve plugins
164
165
 
165
166
  | Plugin | What it does |
166
167
  |---|---|
@@ -174,6 +175,7 @@ grep '<task_id>' .vvoc/specs/*/plan.xml # dependency graph
174
175
  | **ToolHistoryCompactionPlugin** | Shrinks the context replayed to the model by compacting old tool outputs non-destructively, without touching on-disk history. |
175
176
  | **AnalyticsPlugin** | Local-only token and cache telemetry per model step, a live `cache NN%` indicator in the TUI, and `vvoc analytics cache-hit-rate` for retrospective comparison. |
176
177
  | **PeakHoursPlugin** | Warns or blocks models whose provider is in peak-priced hours right now, suggests connected off-peak providers, and shows a persistent orange banner in the TUI. |
178
+ | **SpecGuardPlugin** | Deterministic host-side verification of spec-package artifacts: annotates reads with lint verdicts and validates writes; in enforce mode refuses writes that would break the format. |
177
179
  | **ContextTuiPlugin** | The `/context` inspector: an honest, scrollable TUI dialog showing context-window usage by category, tool, and MCP server. |
178
180
 
179
181
  ### Managed agents
@@ -541,6 +543,34 @@ Config lives in `vvoc.json` under `plugins["peak-hours"]` and is conservatively
541
543
 
542
544
  Windows use `HH:MM` in an explicit timezone (default UTC), may cross midnight (`"start": "22:00", "end": "02:00"`), and accept an optional `days` restriction (0=Sunday … 6=Saturday, default all days). A provider entry may override the global mode with `"mode": "hard"`. Set the top-level `"mode": "hard"` to enforce blocking everywhere, or `"enabled": false` to disable the plugin entirely. Changes require an OpenCode restart, like other runtime plugin settings.
543
545
 
546
+ ### Spec lint and spec-guard
547
+
548
+ The spec-package format is verified by deterministic host-side tooling, not by model discipline: models keep grep/sed as their query layer, and `vvoc lint` plus `SpecGuardPlugin` enforce the invariants in code.
549
+
550
+ ```bash
551
+ vvoc lint # lint .vvoc/specs (active packages only)
552
+ vvoc lint .vvoc/specs/2026-08-24-cache/ # one package directory
553
+ vvoc lint .vvoc/specs/2026-08-24-cache/plan.xml # one file (its sibling spec joins for cross-file rules)
554
+ vvoc lint --archive # include archived packages for migration audits
555
+ vvoc lint --strict # exit 1 on warnings, not only errors
556
+ vvoc lint --format json --no-cache # one JSON verdict object; bypass the cache
557
+ ```
558
+
559
+ The engine checks well-formedness, the attribute ban, the template contract per artifact type, `COMPONENT-UPPER-SLUG` / `TASK-T-NNN` / `WAVE-N` identity patterns and uniqueness, reference integrity (component slugs, task ids), the **plan-components-subset-of-spec-components** rule, lifecycle vocabulary, and package layout. Severity is lifecycle-aware: a `draft` document may have empty sections mid-interview and is never an error; `approved`/`applied` documents must be complete. Exit codes are stable for scripts: `0` without errors, `1` with errors, `--strict` escalates warnings. Results are cached content-addressed under `$XDG_CACHE_HOME/vvoc/lint/` — a verdict is recomputed whenever any input byte or the lint rule version changes.
560
+
561
+ `SpecGuardPlugin` brings the same verdicts into the session:
562
+
563
+ - **reads**: reading an active `spec.xml` / `plan.xml` / `design-context.xml` appends a bounded `[spec-guard]` verdict to the tool result, so agents see drift before trusting the file; archived files are never annotated;
564
+ - **writes**: `warn` (default) appends the verdict after a write; `enforce` additionally refuses writes whose result would contain ERROR-severity findings — incremental draft editing is never blocked, because draft incompleteness is never an error.
565
+
566
+ ```json
567
+ "plugins": {
568
+ "spec-guard": { "enabled": true, "mode": "warn" }
569
+ }
570
+ ```
571
+
572
+ Conservatively materialized by `vvoc sync`/`init`; `enforce` blocks only ERROR states; internal failures degrade to a warning log and never break a tool call. Changes require an OpenCode restart, like other runtime plugin settings.
573
+
544
574
  ### Web tools
545
575
 
546
576
  `WebToolsPlugin` exposes exactly two canonical model-facing tools:
package/dist/cli.js CHANGED
@@ -3,8 +3,8 @@
3
3
  // VERSION: 0.2.10
4
4
  // START_MODULE_CONTRACT
5
5
  // PURPOSE: Assemble and run the vvoc CLI entrypoint.
6
- // SCOPE: Package version lookup, top-level command registration, and main command execution.
7
- // DEPENDS: [citty, src/commands/analytics.ts, src/commands/completion.ts, src/commands/config.ts, src/commands/doctor.ts, src/commands/guardian.ts, src/commands/init.ts, src/commands/install.ts, src/commands/launch.ts, src/commands/orchestration.ts, src/commands/patch-provider.ts, src/commands/preset.ts, src/commands/plugin.ts, src/commands/role.ts, src/commands/status.ts, src/commands/sync.ts, src/commands/upgrade.ts, src/commands/version.ts, src/lib/package.ts]
6
+ // SCOPE: Package version lookup, top-level command registration including the lint command, and main command execution.
7
+ // DEPENDS: [citty, src/commands/analytics.ts, src/commands/completion.ts, src/commands/config.ts, src/commands/doctor.ts, src/commands/guardian.ts, src/commands/init.ts, src/commands/install.ts, src/commands/launch.ts, src/commands/orchestration.ts, src/commands/lint.ts, src/commands/patch-provider.ts, src/commands/preset.ts, src/commands/plugin.ts, src/commands/role.ts, src/commands/status.ts, src/commands/sync.ts, src/commands/upgrade.ts, src/commands/version.ts, src/lib/package.ts]
8
8
  // LINKS: [M-CLI-COMMANDS]
9
9
  // ROLE: SCRIPT
10
10
  // MAP_MODE: LOCALS
@@ -16,7 +16,7 @@
16
16
  // END_MODULE_MAP
17
17
  //
18
18
  // START_CHANGE_SUMMARY
19
- // LAST_CHANGE: [2026-08-19-cache-hit-rate-analytics - Registered the analytics command in the CLI tree.]
19
+ // LAST_CHANGE: [C-SPEC-IDENTITY-LINT - Registered the lint command in the CLI tree.]
20
20
  // END_CHANGE_SUMMARY
21
21
  import { defineCommand, runMain } from "citty";
22
22
  import analytics from "./commands/analytics.js";
@@ -27,6 +27,7 @@ import guardian from "./commands/guardian.js";
27
27
  import init from "./commands/init.js";
28
28
  import install from "./commands/install.js";
29
29
  import launch from "./commands/launch.js";
30
+ import lint from "./commands/lint.js";
30
31
  import orchestration from "./commands/orchestration.js";
31
32
  import patchProvider from "./commands/patch-provider.js";
32
33
  import preset from "./commands/preset.js";
@@ -54,6 +55,7 @@ const main = defineCommand({
54
55
  init,
55
56
  install,
56
57
  launch,
58
+ lint,
57
59
  orchestration,
58
60
  "patch-provider": patchProvider,
59
61
  preset,
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,mBAAmB;AACnB,kBAAkB;AAClB,wBAAwB;AACxB,uDAAuD;AACvD,+FAA+F;AAC/F,sdAAsd;AACtd,4BAA4B;AAC5B,iBAAiB;AACjB,qBAAqB;AACrB,sBAAsB;AACtB,EAAE;AACF,mBAAmB;AACnB,oEAAoE;AACpE,wCAAwC;AACxC,iBAAiB;AACjB,EAAE;AACF,uBAAuB;AACvB,2GAA2G;AAC3G,qBAAqB;AAErB,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAC/C,OAAO,SAAS,MAAM,yBAAyB,CAAC;AAChD,OAAO,UAAU,MAAM,0BAA0B,CAAC;AAClD,OAAO,MAAM,MAAM,sBAAsB,CAAC;AAC1C,OAAO,MAAM,MAAM,sBAAsB,CAAC;AAC1C,OAAO,QAAQ,MAAM,wBAAwB,CAAC;AAC9C,OAAO,IAAI,MAAM,oBAAoB,CAAC;AACtC,OAAO,OAAO,MAAM,uBAAuB,CAAC;AAC5C,OAAO,MAAM,MAAM,sBAAsB,CAAC;AAC1C,OAAO,aAAa,MAAM,6BAA6B,CAAC;AACxD,OAAO,aAAa,MAAM,8BAA8B,CAAC;AACzD,OAAO,MAAM,MAAM,sBAAsB,CAAC;AAC1C,OAAO,MAAM,MAAM,sBAAsB,CAAC;AAC1C,OAAO,IAAI,MAAM,oBAAoB,CAAC;AACtC,OAAO,MAAM,MAAM,sBAAsB,CAAC;AAC1C,OAAO,IAAI,MAAM,oBAAoB,CAAC;AACtC,OAAO,OAAO,MAAM,uBAAuB,CAAC;AAC5C,OAAO,OAAO,MAAM,uBAAuB,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD,iCAAiC;AACjC,MAAM,cAAc,GAAG,MAAM,iBAAiB,EAAE,CAAC;AAEjD,MAAM,IAAI,GAAG,aAAa,CAAC;IACzB,IAAI,EAAE;QACJ,IAAI,EAAE,MAAM;QACZ,OAAO,EAAE,cAAc;QACvB,WAAW,EACT,+HAA+H;KAClI;IACD,WAAW,EAAE;QACX,SAAS;QACT,UAAU;QACV,MAAM;QACN,MAAM;QACN,QAAQ;QACR,IAAI;QACJ,OAAO;QACP,MAAM;QACN,aAAa;QACb,gBAAgB,EAAE,aAAa;QAC/B,MAAM;QACN,MAAM;QACN,IAAI;QACJ,MAAM;QACN,IAAI;QACJ,OAAO;QACP,OAAO;KACR;CACF,CAAC,CAAC;AACH,+BAA+B;AAE/B,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,mBAAmB;AACnB,kBAAkB;AAClB,wBAAwB;AACxB,uDAAuD;AACvD,0HAA0H;AAC1H,4eAA4e;AAC5e,4BAA4B;AAC5B,iBAAiB;AACjB,qBAAqB;AACrB,sBAAsB;AACtB,EAAE;AACF,mBAAmB;AACnB,oEAAoE;AACpE,wCAAwC;AACxC,iBAAiB;AACjB,EAAE;AACF,uBAAuB;AACvB,uFAAuF;AACvF,qBAAqB;AAErB,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAC/C,OAAO,SAAS,MAAM,yBAAyB,CAAC;AAChD,OAAO,UAAU,MAAM,0BAA0B,CAAC;AAClD,OAAO,MAAM,MAAM,sBAAsB,CAAC;AAC1C,OAAO,MAAM,MAAM,sBAAsB,CAAC;AAC1C,OAAO,QAAQ,MAAM,wBAAwB,CAAC;AAC9C,OAAO,IAAI,MAAM,oBAAoB,CAAC;AACtC,OAAO,OAAO,MAAM,uBAAuB,CAAC;AAC5C,OAAO,MAAM,MAAM,sBAAsB,CAAC;AAC1C,OAAO,IAAI,MAAM,oBAAoB,CAAC;AACtC,OAAO,aAAa,MAAM,6BAA6B,CAAC;AACxD,OAAO,aAAa,MAAM,8BAA8B,CAAC;AACzD,OAAO,MAAM,MAAM,sBAAsB,CAAC;AAC1C,OAAO,MAAM,MAAM,sBAAsB,CAAC;AAC1C,OAAO,IAAI,MAAM,oBAAoB,CAAC;AACtC,OAAO,MAAM,MAAM,sBAAsB,CAAC;AAC1C,OAAO,IAAI,MAAM,oBAAoB,CAAC;AACtC,OAAO,OAAO,MAAM,uBAAuB,CAAC;AAC5C,OAAO,OAAO,MAAM,uBAAuB,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD,iCAAiC;AACjC,MAAM,cAAc,GAAG,MAAM,iBAAiB,EAAE,CAAC;AAEjD,MAAM,IAAI,GAAG,aAAa,CAAC;IACzB,IAAI,EAAE;QACJ,IAAI,EAAE,MAAM;QACZ,OAAO,EAAE,cAAc;QACvB,WAAW,EACT,+HAA+H;KAClI;IACD,WAAW,EAAE;QACX,SAAS;QACT,UAAU;QACV,MAAM;QACN,MAAM;QACN,QAAQ;QACR,IAAI;QACJ,OAAO;QACP,MAAM;QACN,IAAI;QACJ,aAAa;QACb,gBAAgB,EAAE,aAAa;QAC/B,MAAM;QACN,MAAM;QACN,IAAI;QACJ,MAAM;QACN,IAAI;QACJ,OAAO;QACP,OAAO;KACR;CACF,CAAC,CAAC;AACH,+BAA+B;AAE/B,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC"}
@@ -0,0 +1,74 @@
1
+ import { type SpecLintVerdict } from "../lib/spec-lint.js";
2
+ export declare const ARTIFACT_FILE_NAMES: readonly ["spec.xml", "plan.xml", "design-context.xml"];
3
+ export interface LintCommandOptions {
4
+ /** File, package directory, or specs tree to lint. Defaults to .vvoc/specs. */
5
+ target: string;
6
+ /** Include archived packages instead of skipping them. */
7
+ includeArchive: boolean;
8
+ /** Escalate warnings to exit failure. */
9
+ strict: boolean;
10
+ /** Bypass the lint cache entirely. */
11
+ noCache: boolean;
12
+ }
13
+ export interface LintArtifactResult {
14
+ verdict: SpecLintVerdict;
15
+ cached: boolean;
16
+ }
17
+ export interface LintRunResult {
18
+ lintVersion: number;
19
+ target: string;
20
+ skippedArchived: number;
21
+ packages: Array<{
22
+ dir: string;
23
+ artifacts: LintArtifactResult[];
24
+ }>;
25
+ }
26
+ export declare const DEFAULT_LINT_TARGET = ".vvoc/specs";
27
+ /**
28
+ * Resolve a lint target into package groups. A file target becomes its own
29
+ * group (plus the sibling spec as a cross-file input for plan targets) with
30
+ * the requested file marked as the reporting primary; a directory target
31
+ * walks recursively and groups artifacts by containing directory. Archived
32
+ * files are counted and skipped unless included explicitly.
33
+ */
34
+ export declare function collectLintTargets(target: string, includeArchive: boolean): Promise<{
35
+ groups: Array<{
36
+ dir: string;
37
+ files: string[];
38
+ primary?: string;
39
+ }>;
40
+ skippedArchived: number;
41
+ }>;
42
+ /** Execute one lint run over resolved targets through the content-addressed cache. */
43
+ export declare function runLint(options: LintCommandOptions, cacheRoot?: string): Promise<LintRunResult>;
44
+ export declare function renderLintReport(result: LintRunResult, strict: boolean): string;
45
+ export declare function computeLintExitCode(result: LintRunResult, strict: boolean): 0 | 1;
46
+ declare const _default: import("citty").CommandDef<{
47
+ readonly path: {
48
+ readonly type: "positional";
49
+ readonly description: "File, package directory, or specs tree to lint (default: .vvoc/specs)";
50
+ readonly fallback: ".vvoc/specs";
51
+ };
52
+ readonly strict: {
53
+ readonly type: "boolean";
54
+ readonly default: false;
55
+ readonly description: "Fail on warnings, not only errors.";
56
+ };
57
+ readonly format: {
58
+ readonly type: "enum";
59
+ readonly options: ["text", "json"];
60
+ readonly default: "text";
61
+ readonly description: "Output format.";
62
+ };
63
+ readonly "no-cache": {
64
+ readonly type: "boolean";
65
+ readonly default: false;
66
+ readonly description: "Bypass the content-addressed lint cache.";
67
+ };
68
+ readonly archive: {
69
+ readonly type: "boolean";
70
+ readonly default: false;
71
+ readonly description: "Include archived packages instead of skipping them.";
72
+ };
73
+ }>;
74
+ export default _default;
@@ -0,0 +1,269 @@
1
+ // FILE: src/commands/lint.ts
2
+ // VERSION: 1.0.0
3
+ // START_MODULE_CONTRACT
4
+ // PURPOSE: Lint .vvoc spec-package XML artifacts (spec.xml, plan.xml, design-context.xml) with lifecycle-aware severity and stable exit codes.
5
+ // SCOPE: Target resolution for a file, package directory, or specs tree; package grouping with cross-file sibling spec inputs; archive skipping with a visible count unless --archive; cache-backed lint runs; text and JSON output; exit code 0 without errors, 1 with errors, --strict escalating warnings.
6
+ // DEPENDS: [citty, node:fs/promises, node:path, src/lib/spec-lint.ts, src/lib/spec-lint-cache.ts]
7
+ // LINKS: [M-CLI-COMMANDS, M-SPEC-LINT]
8
+ // ROLE: RUNTIME
9
+ // MAP_MODE: EXPORTS
10
+ // END_MODULE_CONTRACT
11
+ //
12
+ // START_MODULE_MAP
13
+ // LintCommandOptions - Core lint-run options shared by the CLI and tests.
14
+ // LintArtifactResult - One artifact's verdict plus its cache-hit flag.
15
+ // LintRunResult - Structured result of one lint run over resolved targets.
16
+ // ARTIFACT_FILE_NAMES - Recognized spec-package artifact file names.
17
+ // DEFAULT_LINT_TARGET - Default lint target path (.vvoc/specs).
18
+ // collectLintTargets - Resolve a path into package groups plus an archive skip count.
19
+ // runLint - Execute the lint run over resolved targets through the cache.
20
+ // renderLintReport - Render the text report for a lint run result.
21
+ // computeLintExitCode - Map a run result to the stable exit-code contract.
22
+ // default - vvoc lint command definition.
23
+ // END_MODULE_MAP
24
+ //
25
+ // START_CHANGE_SUMMARY
26
+ // LAST_CHANGE: [C-SPEC-IDENTITY-LINT - Initial command: file/package/tree targets, lifecycle-aware severity, archive skip, cache integration, strict/json flags.]
27
+ // END_CHANGE_SUMMARY
28
+ import { defineCommand } from "citty";
29
+ import { readdir, readFile, stat } from "node:fs/promises";
30
+ import { dirname, join, relative, resolve } from "node:path";
31
+ import { isSpecArchivePath, LINT_VERSION, } from "../lib/spec-lint.js";
32
+ import { createSpecLintCache } from "../lib/spec-lint-cache.js";
33
+ // START_BLOCK_TYPES
34
+ export const ARTIFACT_FILE_NAMES = ["spec.xml", "plan.xml", "design-context.xml"];
35
+ export const DEFAULT_LINT_TARGET = ".vvoc/specs";
36
+ // END_BLOCK_TYPES
37
+ // START_BLOCK_TARGET_RESOLUTION
38
+ async function walkXmlArtifacts(root, base, found) {
39
+ let entries;
40
+ try {
41
+ entries = await readdir(root, { withFileTypes: true });
42
+ }
43
+ catch {
44
+ return;
45
+ }
46
+ for (const entry of entries) {
47
+ const full = join(root, entry.name);
48
+ if (entry.isDirectory()) {
49
+ await walkXmlArtifacts(full, base, found);
50
+ continue;
51
+ }
52
+ if (!entry.isFile())
53
+ continue;
54
+ if (!ARTIFACT_FILE_NAMES.includes(entry.name))
55
+ continue;
56
+ const relativePath = relative(base, full).replace(/\\/g, "/");
57
+ found.push({ path: full, archived: isSpecArchivePath(relativePath) });
58
+ }
59
+ }
60
+ /**
61
+ * Resolve a lint target into package groups. A file target becomes its own
62
+ * group (plus the sibling spec as a cross-file input for plan targets) with
63
+ * the requested file marked as the reporting primary; a directory target
64
+ * walks recursively and groups artifacts by containing directory. Archived
65
+ * files are counted and skipped unless included explicitly.
66
+ */
67
+ export async function collectLintTargets(target, includeArchive) {
68
+ const absolute = resolve(target);
69
+ let targetStat;
70
+ try {
71
+ targetStat = await stat(absolute);
72
+ }
73
+ catch {
74
+ throw new Error(`lint target not found: ${target}`);
75
+ }
76
+ if (targetStat.isFile()) {
77
+ const fileName = absolute.split(/[/\\]/).pop() ?? "";
78
+ if (!ARTIFACT_FILE_NAMES.includes(fileName)) {
79
+ throw new Error(`not a spec-package artifact: ${fileName} (expected ${ARTIFACT_FILE_NAMES.join(", ")})`);
80
+ }
81
+ const files = [absolute];
82
+ if (fileName === "plan.xml") {
83
+ const sibling = join(dirname(absolute), "spec.xml");
84
+ try {
85
+ await stat(sibling);
86
+ files.push(sibling);
87
+ }
88
+ catch {
89
+ // Missing sibling degrades to the engine's spec-missing warning.
90
+ }
91
+ }
92
+ return {
93
+ groups: [{ dir: dirname(absolute), files, primary: absolute.replace(/\\/g, "/") }],
94
+ skippedArchived: 0,
95
+ };
96
+ }
97
+ const found = [];
98
+ await walkXmlArtifacts(absolute, absolute, found);
99
+ const groups = new Map();
100
+ let skippedArchived = 0;
101
+ for (const item of found) {
102
+ if (item.archived && !includeArchive) {
103
+ skippedArchived++;
104
+ continue;
105
+ }
106
+ const dir = dirname(item.path);
107
+ const bucket = groups.get(dir) ?? [];
108
+ bucket.push(item.path);
109
+ groups.set(dir, bucket);
110
+ }
111
+ return {
112
+ groups: [...groups.entries()]
113
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
114
+ .map(([dir, files]) => ({ dir, files })),
115
+ skippedArchived,
116
+ };
117
+ }
118
+ // END_BLOCK_TARGET_RESOLUTION
119
+ // START_BLOCK_RUN
120
+ async function readArtifactInputs(files) {
121
+ const inputs = [];
122
+ for (const file of files.sort()) {
123
+ try {
124
+ const content = await readFile(file, "utf8");
125
+ inputs.push({ file: file.replace(/\\/g, "/"), content });
126
+ }
127
+ catch {
128
+ // Unreadable files degrade to an explicit finding through the engine's unknown path.
129
+ inputs.push({ file: file.replace(/\\/g, "/"), content: "" });
130
+ }
131
+ }
132
+ return inputs;
133
+ }
134
+ /** Execute one lint run over resolved targets through the content-addressed cache. */
135
+ export async function runLint(options, cacheRoot) {
136
+ const cache = await createSpecLintCache({ cacheRoot, bypass: options.noCache });
137
+ const { groups, skippedArchived } = await collectLintTargets(options.target, options.includeArchive);
138
+ const packages = [];
139
+ for (const group of groups) {
140
+ const inputs = await readArtifactInputs(group.files);
141
+ const result = await cache.lint(inputs);
142
+ const byFile = new Map(result.verdicts.map((v) => [v.file, v]));
143
+ const reportFiles = group.primary
144
+ ? group.files.filter((file) => file.replace(/\\/g, "/") === group.primary)
145
+ : group.files;
146
+ const artifacts = reportFiles
147
+ .map((file) => file.replace(/\\/g, "/"))
148
+ .filter((file) => byFile.has(file))
149
+ .map((file) => ({ verdict: byFile.get(file), cached: result.hit }));
150
+ packages.push({ dir: group.dir.replace(/\\/g, "/"), artifacts });
151
+ }
152
+ return { lintVersion: LINT_VERSION, target: options.target, skippedArchived, packages };
153
+ }
154
+ // END_BLOCK_RUN
155
+ // START_BLOCK_REPORT
156
+ export function renderLintReport(result, strict) {
157
+ const lines = [];
158
+ let artifacts = 0;
159
+ let errors = 0;
160
+ let warnings = 0;
161
+ for (const pkg of result.packages) {
162
+ const pkgErrors = pkg.artifacts.reduce((sum, a) => sum + a.verdict.findings.filter((f) => f.severity === "error").length, 0);
163
+ const pkgWarnings = pkg.artifacts.reduce((sum, a) => sum + a.verdict.findings.filter((f) => f.severity === "warning").length, 0);
164
+ artifacts += pkg.artifacts.length;
165
+ errors += pkgErrors;
166
+ warnings += pkgWarnings;
167
+ lines.push(`${pkg.dir}: ${pkg.artifacts.length} artifact(s), ${pkgErrors} error(s), ${pkgWarnings} warning(s)`);
168
+ for (const artifact of pkg.artifacts) {
169
+ const name = artifact.verdict.file.split("/").pop() ?? artifact.verdict.file;
170
+ if (artifact.verdict.findings.length === 0) {
171
+ lines.push(` ${name}: OK${artifact.cached ? " (cached)" : ""}`);
172
+ continue;
173
+ }
174
+ lines.push(` ${name}:${artifact.cached ? " (cached)" : ""}`);
175
+ for (const finding of artifact.verdict.findings) {
176
+ lines.push(` ${artifact.verdict.file}:${finding.line} ${finding.severity} [${finding.rule}]: ${finding.message}`);
177
+ }
178
+ }
179
+ }
180
+ if (result.skippedArchived > 0) {
181
+ lines.push(`Skipped ${result.skippedArchived} archived artifact(s); use --archive to include them.`);
182
+ }
183
+ const fail = errors > 0 || (strict && warnings > 0);
184
+ lines.push(`${artifacts} artifact(s) checked: ${errors} error(s), ${warnings} warning(s) — ${fail ? "FAIL" : "OK"}${strict ? " (strict)" : ""}`);
185
+ return lines.join("\n");
186
+ }
187
+ export function computeLintExitCode(result, strict) {
188
+ const hasErrors = result.packages.some((pkg) => pkg.artifacts.some((a) => a.verdict.findings.some((f) => f.severity === "error")));
189
+ if (hasErrors)
190
+ return 1;
191
+ if (strict) {
192
+ const hasWarnings = result.packages.some((pkg) => pkg.artifacts.some((a) => a.verdict.findings.some((f) => f.severity === "warning")));
193
+ if (hasWarnings)
194
+ return 1;
195
+ }
196
+ return 0;
197
+ }
198
+ // END_BLOCK_REPORT
199
+ // defineCommand wrapper: the run body is the RUN_LINT_COMMAND block below
200
+ export default defineCommand({
201
+ meta: {
202
+ name: "lint",
203
+ description: "Lint .vvoc spec-package artifacts (spec.xml, plan.xml, design-context.xml).",
204
+ },
205
+ args: {
206
+ path: {
207
+ type: "positional",
208
+ description: `File, package directory, or specs tree to lint (default: ${DEFAULT_LINT_TARGET})`,
209
+ fallback: DEFAULT_LINT_TARGET,
210
+ },
211
+ strict: {
212
+ type: "boolean",
213
+ default: false,
214
+ description: "Fail on warnings, not only errors.",
215
+ },
216
+ format: {
217
+ type: "enum",
218
+ options: ["text", "json"],
219
+ default: "text",
220
+ description: "Output format.",
221
+ },
222
+ "no-cache": {
223
+ type: "boolean",
224
+ default: false,
225
+ description: "Bypass the content-addressed lint cache.",
226
+ },
227
+ archive: {
228
+ type: "boolean",
229
+ default: false,
230
+ description: "Include archived packages instead of skipping them.",
231
+ },
232
+ },
233
+ async run({ args }) {
234
+ // START_BLOCK_RUN_LINT_COMMAND
235
+ const options = {
236
+ target: typeof args.path === "string" && args.path ? args.path : DEFAULT_LINT_TARGET,
237
+ includeArchive: args.archive === true,
238
+ strict: args.strict === true,
239
+ noCache: args["no-cache"] === true,
240
+ };
241
+ const result = await runLint(options);
242
+ const strict = options.strict;
243
+ if (args.format === "json") {
244
+ console.log(JSON.stringify({
245
+ lintVersion: result.lintVersion,
246
+ target: result.target,
247
+ ok: computeLintExitCode(result, strict) === 0,
248
+ strict,
249
+ skippedArchived: result.skippedArchived,
250
+ packages: result.packages.map((pkg) => ({
251
+ dir: pkg.dir,
252
+ artifacts: pkg.artifacts.map((a) => ({
253
+ file: a.verdict.file,
254
+ kind: a.verdict.kind,
255
+ ok: a.verdict.ok,
256
+ cached: a.cached,
257
+ findings: a.verdict.findings,
258
+ })),
259
+ })),
260
+ }, null, 2));
261
+ }
262
+ else {
263
+ console.log(renderLintReport(result, strict));
264
+ }
265
+ process.exitCode = computeLintExitCode(result, strict);
266
+ // END_BLOCK_RUN_LINT_COMMAND
267
+ },
268
+ });
269
+ //# sourceMappingURL=lint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lint.js","sourceRoot":"","sources":["../../src/commands/lint.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -8,3 +8,4 @@ export { WebToolsPlugin } from "./plugins/web-tools/index.js";
8
8
  export { ToolHistoryCompactionPlugin } from "./plugins/tool-history-compaction/index.js";
9
9
  export { AnalyticsPlugin } from "./plugins/analytics/index.js";
10
10
  export { PeakHoursPlugin } from "./plugins/peak-hours/index.js";
11
+ export { SpecGuardPlugin } from "./plugins/spec-guard/index.js";
package/dist/index.js CHANGED
@@ -2,19 +2,19 @@
2
2
  // VERSION: 1.0.0
3
3
  // START_MODULE_CONTRACT
4
4
  // PURPOSE: Re-export the public vv-opencode plugin entrypoints from the package root.
5
- // SCOPE: Package-root exports for GuardianPlugin, HashlineEditPlugin, ModelRolesPlugin, SystemContextInjectionPlugin, WorkflowPlugin, SecretsRedactionPlugin, WebToolsPlugin, ToolHistoryCompactionPlugin, AnalyticsPlugin, and PeakHoursPlugin.
6
- // DEPENDS: [src/plugins/guardian/index.ts, src/plugins/hashline-edit/index.ts, src/plugins/model-roles/index.ts, src/plugins/system-context-injection/index.ts, src/plugins/workflow/index.ts, src/plugins/secrets-redaction.ts, src/plugins/web-tools/index.ts, src/plugins/tool-history-compaction/index.ts, src/plugins/analytics/index.ts, src/plugins/peak-hours/index.ts]
7
- // LINKS: [M-PLUGIN-GUARDIAN, M-PLUGIN-HASHLINE-EDIT, M-PLUGIN-MODEL-ROLES, M-PLUGIN-SYSTEM-CONTEXT-INJECTION, M-PLUGIN-WORKFLOW, M-PLUGIN-SECRETS-REDACTION, M-PLUGIN-WEB-TOOLS, M-PLUGIN-TOOL-HISTORY-COMPACTION, M-PLUGIN-ANALYTICS, M-PLUGIN-PEAK-HOURS]
5
+ // SCOPE: Package-root exports for GuardianPlugin, HashlineEditPlugin, ModelRolesPlugin, SystemContextInjectionPlugin, WorkflowPlugin, SecretsRedactionPlugin, WebToolsPlugin, ToolHistoryCompactionPlugin, AnalyticsPlugin, PeakHoursPlugin, and SpecGuardPlugin.
6
+ // DEPENDS: [src/plugins/guardian/index.ts, src/plugins/hashline-edit/index.ts, src/plugins/model-roles/index.ts, src/plugins/system-context-injection/index.ts, src/plugins/workflow/index.ts, src/plugins/secrets-redaction.ts, src/plugins/web-tools/index.ts, src/plugins/tool-history-compaction/index.ts, src/plugins/analytics/index.ts, src/plugins/peak-hours/index.ts, src/plugins/spec-guard/index.ts]
7
+ // LINKS: [M-PLUGIN-GUARDIAN, M-PLUGIN-HASHLINE-EDIT, M-PLUGIN-MODEL-ROLES, M-PLUGIN-SYSTEM-CONTEXT-INJECTION, M-PLUGIN-WORKFLOW, M-PLUGIN-SECRETS-REDACTION, M-PLUGIN-WEB-TOOLS, M-PLUGIN-TOOL-HISTORY-COMPACTION, M-PLUGIN-ANALYTICS, M-PLUGIN-PEAK-HOURS, M-PLUGIN-SPEC-GUARD]
8
8
  // ROLE: BARREL
9
9
  // MAP_MODE: SUMMARY
10
10
  // END_MODULE_CONTRACT
11
11
  //
12
12
  // START_MODULE_MAP
13
- // GuardianPlugin, HashlineEditPlugin, ModelRolesPlugin, SystemContextInjectionPlugin, WorkflowPlugin, SecretsRedactionPlugin, WebToolsPlugin, ToolHistoryCompactionPlugin, AnalyticsPlugin, PeakHoursPlugin - Public plugin exports available from @osovv/vv-opencode.
13
+ // GuardianPlugin, HashlineEditPlugin, ModelRolesPlugin, SystemContextInjectionPlugin, WorkflowPlugin, SecretsRedactionPlugin, WebToolsPlugin, ToolHistoryCompactionPlugin, AnalyticsPlugin, PeakHoursPlugin, SpecGuardPlugin - Public plugin exports available from @osovv/vv-opencode.
14
14
  // END_MODULE_MAP
15
15
  //
16
16
  // START_CHANGE_SUMMARY
17
- // LAST_CHANGE: [C-PLUGIN-PEAK-HOURS - Added PeakHoursPlugin to the package-root exports.]
17
+ // LAST_CHANGE: [C-SPEC-IDENTITY-LINT - Added SpecGuardPlugin to the package-root exports.]
18
18
  // END_CHANGE_SUMMARY
19
19
  export { GuardianPlugin } from "./plugins/guardian/index.js";
20
20
  export { HashlineEditPlugin } from "./plugins/hashline-edit/index.js";
@@ -26,4 +26,5 @@ export { WebToolsPlugin } from "./plugins/web-tools/index.js";
26
26
  export { ToolHistoryCompactionPlugin } from "./plugins/tool-history-compaction/index.js";
27
27
  export { AnalyticsPlugin } from "./plugins/analytics/index.js";
28
28
  export { PeakHoursPlugin } from "./plugins/peak-hours/index.js";
29
+ export { SpecGuardPlugin } from "./plugins/spec-guard/index.js";
29
30
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qBAAqB;AACrB,iBAAiB;AACjB,wBAAwB;AACxB,wFAAwF;AACxF,mPAAmP;AACnP,kXAAkX;AAClX,8PAA8P;AAC9P,iBAAiB;AACjB,sBAAsB;AACtB,sBAAsB;AACtB,EAAE;AACF,mBAAmB;AACnB,yQAAyQ;AACzQ,iBAAiB;AACjB,EAAE;AACF,uBAAuB;AACvB,4FAA4F;AAC5F,qBAAqB;AAErB,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,EAAE,4BAA4B,EAAE,MAAM,6CAA6C,CAAC;AAC3F,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,2BAA2B,EAAE,MAAM,4CAA4C,CAAC;AACzF,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,qBAAqB;AACrB,iBAAiB;AACjB,wBAAwB;AACxB,wFAAwF;AACxF,oQAAoQ;AACpQ,mZAAmZ;AACnZ,mRAAmR;AACnR,iBAAiB;AACjB,sBAAsB;AACtB,sBAAsB;AACtB,EAAE;AACF,mBAAmB;AACnB,0RAA0R;AAC1R,iBAAiB;AACjB,EAAE;AACF,uBAAuB;AACvB,6FAA6F;AAC7F,qBAAqB;AAErB,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,kCAAkC,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAClE,OAAO,EAAE,4BAA4B,EAAE,MAAM,6CAA6C,CAAC;AAC3F,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,2BAA2B,EAAE,MAAM,4CAA4C,CAAC;AACzF,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC"}
@@ -65,7 +65,7 @@
65
65
  // END_MODULE_MAP
66
66
  //
67
67
  // START_CHANGE_SUMMARY
68
- // LAST_CHANGE: [C-PLUGIN-PEAK-HOURS - Materialized the conservative peak-hours plugin entry during install, init, and sync writes.]
68
+ // LAST_CHANGE: [C-SPEC-IDENTITY-LINT - Materialized the conservative spec-guard plugin entry during install, init, and sync writes.]
69
69
  // END_CHANGE_SUMMARY
70
70
  import { applyEdits, format, modify, parse } from "jsonc-parser";
71
71
  import { lstat, mkdir, readFile, readlink, symlink, unlink, writeFile } from "node:fs/promises";
@@ -77,7 +77,7 @@ import { resolveOrchestrationPolicy } from "./orchestration.js";
77
77
  import { createDefaultVvocConfig, createGuardianConfig, parseVvocConfigText, parseVersionedVvocConfigText, renderVvocConfig, } from "./vvoc-config.js";
78
78
  import { getPinnedPackageSpecifier, PACKAGE_NAME, PACKAGE_VERSION } from "./package.js";
79
79
  import { resolveConfigWriteTargets, resolveOpenCodeConfigSource, resolveOpenCodeTuiConfigSource, resolveVvocConfigSource, } from "./config-layers.js";
80
- import { materializeHashlineEditEntry, materializeToolHistoryCompactionEntry, materializePeakHoursEntry, } from "./plugin-toggle-config.js";
80
+ import { materializeHashlineEditEntry, materializeToolHistoryCompactionEntry, materializePeakHoursEntry, materializeSpecGuardEntry, } from "./plugin-toggle-config.js";
81
81
  import { getConfigHome, getGlobalOpencodeSkillsDir, getGlobalVvocDir, getVvocAgentsDir, getVvocSkillsDir, } from "./vvoc-paths.js";
82
82
  export const CLI_NAME = "vvoc";
83
83
  export { PACKAGE_NAME };
@@ -616,6 +616,7 @@ export async function syncVvocConfig(paths) {
616
616
  nextConfig.plugins["hashline-edit"] = materializeHashlineEditEntry(nextConfig.plugins["hashline-edit"]);
617
617
  nextConfig.plugins["tool-history-compaction"] = materializeToolHistoryCompactionEntry(nextConfig.plugins["tool-history-compaction"]);
618
618
  nextConfig.plugins["peak-hours"] = materializePeakHoursEntry(nextConfig.plugins["peak-hours"]);
619
+ nextConfig.plugins["spec-guard"] = materializeSpecGuardEntry(nextConfig.plugins["spec-guard"]);
619
620
  return writeResolvedVvocConfig(paths.vvocConfigPath, currentText, nextConfig);
620
621
  }
621
622
  export async function writeGuardianConfig(paths, overrides, options = {}) {