omniconductor 0.6.0 → 1.0.1

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 (48) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +16 -12
  3. package/VISION.md +2 -2
  4. package/adapters/README.md +8 -8
  5. package/adapters/claude/README.md +2 -5
  6. package/adapters/claude/SUPPORTED-FEATURES.md +1 -1
  7. package/adapters/claude/metadata.json +39 -0
  8. package/adapters/claude/transform.sh +198 -34
  9. package/adapters/codex/README.md +23 -27
  10. package/adapters/codex/metadata.json +35 -0
  11. package/adapters/codex/transform-spec.md +5 -6
  12. package/adapters/codex/transform.sh +282 -35
  13. package/adapters/copilot/README.md +26 -29
  14. package/adapters/copilot/metadata.json +36 -0
  15. package/adapters/copilot/transform.sh +124 -33
  16. package/adapters/cursor/README.md +31 -30
  17. package/adapters/cursor/metadata.json +35 -0
  18. package/adapters/cursor/transform.sh +115 -26
  19. package/adapters/gemini/README.md +14 -15
  20. package/adapters/gemini/metadata.json +36 -0
  21. package/adapters/gemini/transform.sh +310 -34
  22. package/adapters/windsurf/README.md +20 -19
  23. package/adapters/windsurf/metadata.json +36 -0
  24. package/adapters/windsurf/transform.sh +137 -53
  25. package/bin/doctor.js +257 -0
  26. package/bin/omniconductor.js +15 -2
  27. package/core/anti-patterns/frequent-rule-file-edit.md +1 -1
  28. package/core/anti-patterns/single-monolithic-rule-file.md +1 -1
  29. package/core/recipes/README.md +2 -2
  30. package/core/universal-rules/README.md +4 -4
  31. package/core/universal-rules/meta-discipline.md +4 -4
  32. package/core/universal-rules/spec-as-you-go.md +1 -1
  33. package/docs/ADAPTER-LIVE-VERIFICATION.md +73 -0
  34. package/docs/COMPARISON.md +133 -0
  35. package/docs/COMPATIBILITY-MATRIX.md +126 -0
  36. package/docs/DESIGN-DECISIONS.md +1268 -0
  37. package/docs/MANUAL-INSTALL.md +15 -15
  38. package/docs/PUBLICATION-POLICY.md +46 -0
  39. package/docs/PUBLISH-GUIDE.md +141 -0
  40. package/package.json +7 -1
  41. package/tools/check-adapter-metadata.sh +211 -0
  42. package/tools/check-stale-tokens.sh +130 -0
  43. package/tools/generate-adapter-docs.js +123 -0
  44. package/tools/live-verify.sh +195 -0
  45. package/tools/manifest-safety.sh +118 -0
  46. package/tools/stale-tokens.txt +32 -0
  47. package/tools/test-install-modes.sh +277 -0
  48. package/tools/validate-adapter-output.sh +1 -1
@@ -2,12 +2,12 @@
2
2
 
3
3
  > Companion to `README.md`. Step-by-step manual install for every supported tool, on Mac and Windows.
4
4
 
5
- This document is the fallback when the per-tool `transform.sh` adapter is not yet available, when you want to understand exactly what the adapter does, or when you are in a constrained environment (no bash, locked-down corporate machine, etc.) and need to copy files by hand.
5
+ This document is the fallback for when you want to understand exactly what the adapter does, or when you are in a constrained environment (no bash, locked-down corporate machine, etc.) and need to copy files by hand.
6
6
 
7
7
  For each tool there are two paths:
8
8
 
9
- - **Adapter (recommended)** — a single `bash transform.sh <target>` invocation. Available now for Claude Code, Cursor, GitHub Copilot.
10
- - **Manual file copy (fallback)** — explicit `cp` / `cat` commands and frontmatter cheat sheet. Available for every tool, including the ones whose adapters have not shipped.
9
+ - **Adapter (recommended)** — `npx omniconductor init --target=<tool> <dir>` or a single `bash transform.sh <target>` invocation. Available for **all six tools** (Claude Code, Cursor, GitHub Copilot, Gemini CLI, Codex, Windsurf).
10
+ - **Manual file copy (fallback)** — explicit `cp` / `cat` commands and frontmatter cheat sheet.
11
11
 
12
12
  > **Read first**: [`README.md`](../README.md) Quick Start. The Claude adapter has a guided wizard that handles 90% of installs. Manual install is for the remaining 10%.
13
13
 
@@ -30,14 +30,14 @@ For each tool there are two paths:
30
30
 
31
31
  | Tool | Adapter ready | Recommended install |
32
32
  |---|---|---|
33
- | Claude Code | ✅ | `bash adapters/claude/transform.sh <target>` (see README) |
34
- | Cursor | ✅ (P2 in flight) | `bash adapters/cursor/transform.sh <target>` |
35
- | GitHub Copilot | ✅ (P2 in flight) | `bash adapters/copilot/transform.sh <target>` |
36
- | Gemini CLI | | manual copy (this doc) |
37
- | Codex (OpenAI) | | manual copy (this doc) |
38
- | Windsurf | | manual copy (this doc) |
33
+ | Claude Code | ✅ | `npx omniconductor init --target=claude <target>` (see README) |
34
+ | Cursor | ✅ | `npx omniconductor init --target=cursor <target>` |
35
+ | GitHub Copilot | ✅ | `npx omniconductor init --target=copilot <target>` |
36
+ | Gemini CLI | | `npx omniconductor init --target=gemini <target>` |
37
+ | Codex (OpenAI) | | `npx omniconductor init --target=codex <target>` |
38
+ | Windsurf | | `npx omniconductor init --target=windsurf <target>` |
39
39
 
40
- > Adapter ship status as of 2026-05-10. Cursor and Copilot adapters are in active development under separate dispatches. If `adapters/<tool>/transform.sh` exists and is executable, prefer it; otherwise follow the manual section for that tool.
40
+ > All six adapters ship `transform.sh` and the npm CLI has existed since v0.2. Prefer the adapter it handles backups, the install manifest, and `--uninstall`. Use the manual sections below only for constrained environments.
41
41
 
42
42
  ---
43
43
 
@@ -66,7 +66,7 @@ Each tool consumes a different syntax. When you copy a rule file into a tool-nat
66
66
 
67
67
  ---
68
68
 
69
- ## Tool 1 — Cursor (manual, until P2 adapter ships)
69
+ ## Tool 1 — Cursor (fallback manual install)
70
70
 
71
71
  ### Prerequisites
72
72
 
@@ -164,7 +164,7 @@ rm -f .cursorrules
164
164
 
165
165
  ---
166
166
 
167
- ## Tool 2 — GitHub Copilot (manual, until P2 adapter ships)
167
+ ## Tool 2 — GitHub Copilot (fallback manual install)
168
168
 
169
169
  GitHub Copilot supports custom instructions via `.github/instructions/*.instructions.md` files, which are picked up by the Copilot extension across **VSCode, Cursor, Windsurf, JetBrains, Neovim** simultaneously (one install covers all five IDEs that have the Copilot extension).
170
170
 
@@ -281,7 +281,7 @@ Get-ChildItem C:\conductor\core\universal-rules\*.md |
281
281
  ### Limitations
282
282
 
283
283
  - No lazy load — every rule is loaded every turn (token cost higher than Claude/Cursor/Copilot).
284
- - No hooks, no sub-agents (per ADR-004 honesty principle).
284
+ - Manual install ships rule text only — no hook configs or agents (the tool supports hooks/sub-agents natively, ADR-031; the bash adapter emits the opt-in Reflector hook, broader emission is Phase 2 — ADR-034).
285
285
 
286
286
  ### Uninstall
287
287
 
@@ -330,7 +330,7 @@ None — Codex ignores YAML frontmatter. Optional cleanup: strip `---...---` blo
330
330
  ### Limitations
331
331
 
332
332
  - Single bundled file, no per-pattern routing.
333
- - No hooks, no sub-agents.
333
+ - Manual install ships rule text only — no hook configs or agents (the tool supports hooks/sub-agents natively, ADR-031; the bash adapter emits the opt-in Reflector hook, broader emission is Phase 2 — ADR-034).
334
334
 
335
335
  ### Uninstall
336
336
 
@@ -385,7 +385,7 @@ Windsurf rule format is **NOT VERIFIED for v0.2** — check the Windsurf docs fo
385
385
  ### Limitations
386
386
 
387
387
  - Per-pattern routing support is version-dependent — likely degrades to "all loaded all the time".
388
- - No hooks, no sub-agents.
388
+ - Manual install ships rule text only — no hook configs or agents (the tool supports hooks/sub-agents natively, ADR-031; the bash adapter emits the opt-in Reflector hook, broader emission is Phase 2 — ADR-034).
389
389
 
390
390
  ### Uninstall
391
391
 
@@ -0,0 +1,46 @@
1
+ # Publication Policy — private source, public distribution
2
+
3
+ ## Decision
4
+
5
+ CONDUCTOR uses a **dual-repository model**:
6
+
7
+ | Surface | Visibility | Purpose |
8
+ |---|---|---|
9
+ | Working source repository | Private | Development, operational history, internal audits/plans, release safety controls, and the private-token deny list. |
10
+ | `omniconductor` mirror | Public | User-visible source, GitHub Releases, issue/discussion entry point, and the exact tree used for npm publication. |
11
+
12
+ The working source repository **must never be made public**. Changing its
13
+ visibility would expose all historical commits, including artifacts that a
14
+ current file filter cannot remove. A clean public repository is produced only
15
+ by the filtered-mirror process below.
16
+
17
+ ## Enforced publication boundary
18
+
19
+ `scripts/sync-public.sh` creates a snapshot from committed private `main` and
20
+ fails closed before any push when either control fails:
21
+
22
+ 1. A structural DENY list removes private-only paths, including operational
23
+ session files, maintainer scripts, internal audit/plan/spec/data directories,
24
+ and the private-token list itself.
25
+ 2. Every private token is scanned across the entire filtered snapshot. One hit
26
+ aborts the operation even if the affected file was not anticipated by the
27
+ DENY list.
28
+ 3. The framework-purity gate must pass.
29
+
30
+ The private CI workflow runs `bash scripts/sync-public.sh HEAD --check`. This
31
+ is an offline, no-push verification of those three controls for every mergeable
32
+ commit. The actual sync remains human-triggered and requires `--push`.
33
+
34
+ ## Required v1.0.1 release order
35
+
36
+ 1. Commit and test the change in the private source repository.
37
+ 2. Let private CI pass, including the public-snapshot safety job.
38
+ 3. Run `bash scripts/sync-public.sh main --push --release v1.0.1` from the
39
+ private source repository.
40
+ 4. Clone or otherwise check out the resulting public `v1.0.1` release.
41
+ 5. From that public checkout, run `npm pack --dry-run`, then the interactive
42
+ `npm publish` command.
43
+ 6. Verify `npm view omniconductor version` reports `1.0.1`.
44
+
45
+ Feature branches and uncommitted working trees are never synchronized. A new
46
+ internal document must be added to the sync script's DENY list before release.
@@ -0,0 +1,141 @@
1
+ # Conductor — Marketplace Publish Guide
2
+
3
+ How to package and publish the Phase 2 VSCode extension to the **VSCode Marketplace** (Microsoft) and the **Open VSX Registry** (Eclipse Foundation, used by Cursor + open-source forks).
4
+
5
+ This is a **manual procedure** — the extension code lives in `phase-2/vscode-extension/`, but registry credentials are personal/organizational secrets and never live in the repo.
6
+
7
+ ---
8
+
9
+ ## 0. Prerequisites (one-time)
10
+
11
+ ```bash
12
+ cd phase-2/vscode-extension
13
+ npm install
14
+ npm install -g @vscode/vsce ovsx # or use npx
15
+ ```
16
+
17
+ Verify the icon exists at `images/icon.png` (replace the placeholder before first publish — see CHANGELOG).
18
+
19
+ ## 1. Local build + package
20
+
21
+ ```bash
22
+ cd phase-2/vscode-extension
23
+ npm run compile
24
+ npx vsce package
25
+ ```
26
+
27
+ Output: `conductor-0.3.0.vsix` in the extension dir. Expected size: < 200 KB (no bundled rules — the extension only ships compiled JS).
28
+
29
+ Smoke-test the .vsix locally before publishing:
30
+
31
+ ```bash
32
+ code --install-extension conductor-0.3.0.vsix
33
+ # In VSCode: Cmd/Ctrl+Shift+P → "Conductor: Install" should appear.
34
+ code --uninstall-extension lfamily-labs.conductor
35
+ ```
36
+
37
+ ---
38
+
39
+ ## 2. VSCode Marketplace publish (Microsoft)
40
+
41
+ ### 2.1 Publisher account (one-time)
42
+
43
+ 1. Sign in at https://dev.azure.com with a Microsoft account.
44
+ 2. Create an organization (any name, used internally only).
45
+ 3. Open https://marketplace.visualstudio.com/manage and create a publisher with **Publisher ID `lfamily-labs`** (matches `package.json` `"publisher"`).
46
+ 4. Sign the Microsoft Marketplace Publisher Agreement.
47
+
48
+ ### 2.2 Personal Access Token (PAT)
49
+
50
+ 1. Azure DevOps → User settings → Personal access tokens → New token.
51
+ 2. **Organization**: All accessible organizations.
52
+ 3. **Scopes**: custom-defined → **Marketplace → Manage** (everything else off).
53
+ 4. **Expiration**: 1 year.
54
+ 5. Copy the token — it is shown once.
55
+
56
+ Store the PAT in 1Password (or your secrets vault of choice) under `Conductor / VSCode Marketplace PAT` — same convention you use for other long-lived publisher credentials.
57
+
58
+ ### 2.3 Login + publish
59
+
60
+ ```bash
61
+ cd phase-2/vscode-extension
62
+ npx vsce login lfamily-labs
63
+ # Paste the PAT when prompted.
64
+
65
+ npx vsce publish
66
+ # Or publish a specific .vsix:
67
+ # npx vsce publish --packagePath conductor-0.3.0.vsix
68
+ ```
69
+
70
+ Verify: https://marketplace.visualstudio.com/items?itemName=lfamily-labs.conductor (5-10 min indexing delay).
71
+
72
+ ---
73
+
74
+ ## 3. Open VSX Registry publish (Cursor / forks)
75
+
76
+ ### 3.1 Publisher account (one-time)
77
+
78
+ 1. Sign in at https://open-vsx.org with GitHub.
79
+ 2. Create a namespace `lfamily-labs` (must match `package.json` `"publisher"`).
80
+ 3. Sign the [Eclipse Foundation Publisher Agreement](https://open-vsx.org/user-settings/profile) — required before first publish.
81
+
82
+ ### 3.2 Personal Access Token
83
+
84
+ 1. https://open-vsx.org/user-settings/tokens → Generate new token.
85
+ 2. Description: `Conductor publish`.
86
+ 3. Copy the token.
87
+
88
+ Store in 1Password under `Conductor / Open VSX PAT`.
89
+
90
+ ### 3.3 Publish
91
+
92
+ ```bash
93
+ cd phase-2/vscode-extension
94
+ npx ovsx create-namespace lfamily-labs -p <OPEN_VSX_PAT> # one-time
95
+ npx ovsx publish conductor-0.3.0.vsix -p <OPEN_VSX_PAT>
96
+ ```
97
+
98
+ Verify: https://open-vsx.org/extension/lfamily-labs/conductor
99
+
100
+ ---
101
+
102
+ ## 4. Updating to a new version
103
+
104
+ Per release:
105
+
106
+ 1. Bump `package.json` `version` (semver).
107
+ 2. Add a `## [x.y.z] — YYYY-MM-DD` block in `CHANGELOG.md`.
108
+ 3. Build + package:
109
+ ```bash
110
+ npm run compile && npx vsce package
111
+ ```
112
+ 4. Cross-publish:
113
+ ```bash
114
+ npx vsce publish
115
+ npx ovsx publish conductor-<version>.vsix -p <OPEN_VSX_PAT>
116
+ ```
117
+ 5. Tag in git: `git tag vscode-ext-v<version> && git push --tags`.
118
+
119
+ Both marketplaces accept the same `.vsix` artifact — no separate build per registry.
120
+
121
+ ---
122
+
123
+ ## 5. Common publish failures
124
+
125
+ | Symptom | Fix |
126
+ |---|---|
127
+ | `vsce` rejects with "missing repository" | Ensure `package.json` `repository.url` is set + reachable. |
128
+ | `ovsx` rejects with "namespace not found" | Run `ovsx create-namespace` once. |
129
+ | Marketplace shows extension but icon is blank | Replace `images/icon.png` placeholder with a 128x128 square PNG. |
130
+ | PAT expired mid-publish | Regenerate (Section 2.2 / 3.2), re-login. |
131
+ | `Cannot publish — manifest contains 'name' that doesn't match publisher` | `package.json` `publisher` must equal the registered publisher ID exactly. |
132
+
133
+ ---
134
+
135
+ ## 6. Roadmap (post-marketplace)
136
+
137
+ - **Auto-update telemetry** — opt-in install count via `vsce` analytics dashboard.
138
+ - **CI publishing** — GitHub Action that publishes on tag push (deferred until Phase 3, gated on the adopter project's CI-quota recovery).
139
+ - **JetBrains plugin** — separate registry, separate dispatch (ADR-025 "out of scope").
140
+
141
+ References: ADR-023 (marketplace strategy), ADR-025 (extension architecture).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omniconductor",
3
- "version": "0.6.0",
3
+ "version": "1.0.1",
4
4
  "description": "Multi-coding-agent workflow framework. Write your project's rules, workflow, and discipline ONCE — install into Claude Code, Cursor, GitHub Copilot, Gemini CLI, Codex, or Windsurf. Encodes the discipline developers most often skip — spec-before-code, review gates, session continuity — so your AI agents catch what's easy to miss.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "LFamily Labs LLC",
@@ -26,6 +26,12 @@
26
26
  "docs/MANUAL-INSTALL.md",
27
27
  "docs/PROMPT-CACHING-GUIDE.md",
28
28
  "docs/CONTEXT-EDITING-GUIDE.md",
29
+ "docs/COMPATIBILITY-MATRIX.md",
30
+ "docs/ADAPTER-LIVE-VERIFICATION.md",
31
+ "docs/PUBLISH-GUIDE.md",
32
+ "docs/PUBLICATION-POLICY.md",
33
+ "docs/DESIGN-DECISIONS.md",
34
+ "docs/COMPARISON.md",
29
35
  "README.md",
30
36
  "VISION.md",
31
37
  "LICENSE",
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/env bash
2
+ # CONDUCTOR adapter-metadata consistency check (ADR-040).
3
+ # Exit 0 = consistent, 1 = drift/inconsistency found, 2 = checker error.
4
+ #
5
+ # adapters/<tool>/metadata.json is the SINGLE SOURCE for enumerable adapter facts
6
+ # (output paths, legacy paths, tier, capabilities, live-verification, headless CLI).
7
+ # This checker asserts the places that state those facts agree with it:
8
+ #
9
+ # M1: metadata.json exists for all 6 adapters, is valid JSON, has required keys —
10
+ # including NON-EMPTY nested fields (tier, live_verification.status,
11
+ # headless_cli.command, at least one output with a path)
12
+ # M2: every outputs[].path literal appears in adapters/<tool>/transform.sh
13
+ # M3: every outputs[].path with "validated": true appears in tools/validate-adapter-output.sh
14
+ # M4: every reflector_outputs[].path literal appears in adapters/<tool>/transform.sh
15
+ # M5: every legacy_paths[] literal is at least MENTIONED in transform.sh or the
16
+ # validator (legacy awareness in code/comments — a substring check, not proof of
17
+ # functional migration handling)
18
+ # M6: live_verification.status == "verified" requires a date, and that date must
19
+ # appear on a line of docs/ADAPTER-LIVE-VERIFICATION.md that names the tool
20
+ # (fixed-string matching; single-sourced verification claims)
21
+ # M7: headless_cli.command is in the `for c in ...` auto-detect list of
22
+ # core/reflector/run-weekly.sh (word match on that line — comments don't count)
23
+ # M8: tier — the COMPATIBILITY-MATRIX tier-assignment TABLE ROW (a line starting
24
+ # with '| **<tier> — ') must name the adapter's display_name
25
+ # M9: install.ala_carte strategy matches the code — "block" iff transform.sh
26
+ # contains the conductor:block marker machinery (ADR-044)
27
+ #
28
+ # Dependency: node (already required by the CLI + CI). No jq.
29
+
30
+ set -u
31
+
32
+ cd "$(dirname "$0")/.." || exit 2
33
+
34
+ command -v node >/dev/null 2>&1 || { echo "ERROR: node is required" >&2; exit 2; }
35
+
36
+ TOOLS="claude cursor copilot gemini codex windsurf"
37
+ VALIDATOR="tools/validate-adapter-output.sh"
38
+ LIVE_DOC="docs/ADAPTER-LIVE-VERIFICATION.md"
39
+ MATRIX_DOC="docs/COMPATIBILITY-MATRIX.md"
40
+ RUNNER="core/reflector/run-weekly.sh"
41
+
42
+ FAIL=0
43
+ fail() { echo "FAIL[$1] $2"; FAIL=1; }
44
+ ok() { echo "OK [$1] $2"; }
45
+
46
+ # Flatten metadata.json to TSV lines via node (validates nested completeness):
47
+ # OUTPUT<TAB>path<TAB>validated
48
+ # REFLECTOR<TAB>path
49
+ # LEGACY<TAB>path
50
+ # FIELD<TAB>key<TAB>value
51
+ flatten_metadata() {
52
+ node -e '
53
+ const fs = require("fs");
54
+ const m = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
55
+ const die = (msg) => { console.error("INCOMPLETE: " + msg); process.exit(3); };
56
+ const req = ["tool","display_name","tier","outputs","reflector_outputs","legacy_paths","capabilities","live_verification","headless_cli"];
57
+ for (const k of req) if (!(k in m)) die("missing key " + k);
58
+ const nonEmpty = (v, name) => { if (typeof v !== "string" || !v.trim()) die(name + " must be a non-empty string"); };
59
+ nonEmpty(m.tool, "tool"); nonEmpty(m.display_name, "display_name"); nonEmpty(m.tier, "tier");
60
+ if (!Array.isArray(m.outputs) || m.outputs.length === 0) die("outputs must be a non-empty array");
61
+ for (const o of m.outputs) nonEmpty(o.path, "outputs[].path");
62
+ if (!Array.isArray(m.reflector_outputs)) die("reflector_outputs must be an array");
63
+ for (const r of m.reflector_outputs) nonEmpty(r.path, "reflector_outputs[].path");
64
+ if (!Array.isArray(m.legacy_paths)) die("legacy_paths must be an array");
65
+ for (const l of m.legacy_paths) nonEmpty(l, "legacy_paths[]");
66
+ if (!m.capabilities || !m.capabilities.tool_native || !m.capabilities.conductor_emitted) die("capabilities needs tool_native + conductor_emitted");
67
+ if (!m.live_verification) die("live_verification missing");
68
+ nonEmpty(m.live_verification.status, "live_verification.status");
69
+ if (m.live_verification.status === "verified") nonEmpty(m.live_verification.date, "live_verification.date (required when verified)");
70
+ if (!m.headless_cli) die("headless_cli missing");
71
+ nonEmpty(m.headless_cli.command, "headless_cli.command");
72
+ if (!m.install || (m.install.ala_carte !== "block" && m.install.ala_carte !== "per-file")) die("install.ala_carte must be block|per-file");
73
+ for (const o of m.outputs) console.log(["OUTPUT", o.path, o.validated ? "true" : "false"].join("\t"));
74
+ for (const r of m.reflector_outputs) console.log(["REFLECTOR", r.path].join("\t"));
75
+ for (const l of m.legacy_paths) console.log(["LEGACY", l].join("\t"));
76
+ console.log(["FIELD","tool",m.tool].join("\t"));
77
+ console.log(["FIELD","display_name",m.display_name].join("\t"));
78
+ console.log(["FIELD","tier",m.tier].join("\t"));
79
+ console.log(["FIELD","live_status",m.live_verification.status].join("\t"));
80
+ console.log(["FIELD","live_date",m.live_verification.date || ""].join("\t"));
81
+ console.log(["FIELD","headless_command",m.headless_cli.command].join("\t"));
82
+ console.log(["FIELD","ala_carte",m.install.ala_carte].join("\t"));
83
+ ' "$1"
84
+ }
85
+
86
+ for tool in $TOOLS; do
87
+ meta="adapters/$tool/metadata.json"
88
+ transform="adapters/$tool/transform.sh"
89
+
90
+ # M1 — exists + valid JSON + required keys + non-empty nested fields
91
+ if [ ! -f "$meta" ]; then
92
+ fail "M1" "$meta missing"
93
+ continue
94
+ fi
95
+ if ! flat="$(flatten_metadata "$meta" 2>&1)"; then
96
+ fail "M1" "$meta invalid or incomplete: $(printf '%s' "$flat" | head -1)"
97
+ continue
98
+ fi
99
+ ok "M1" "$meta valid + complete"
100
+
101
+ tier=""; display=""
102
+ live_status=""; live_date=""; headless=""; ala_carte=""
103
+ while IFS=$'\t' read -r kind a b; do
104
+ case "$kind" in
105
+ OUTPUT)
106
+ # M2 — path literal present in transform.sh
107
+ if grep -qF -- "$a" "$transform"; then
108
+ ok "M2" "$tool: transform.sh mentions '$a'"
109
+ else
110
+ fail "M2" "$tool: outputs path '$a' NOT found in $transform (metadata drift?)"
111
+ fi
112
+ # M3 — validated paths must be known to the validator
113
+ if [ "$b" = "true" ]; then
114
+ if grep -qF -- "$a" "$VALIDATOR"; then
115
+ ok "M3" "$tool: validator covers '$a'"
116
+ else
117
+ fail "M3" "$tool: validated path '$a' NOT found in $VALIDATOR"
118
+ fi
119
+ fi
120
+ ;;
121
+ REFLECTOR)
122
+ # M4 — reflector path literal present in transform.sh
123
+ if grep -qF -- "$a" "$transform"; then
124
+ ok "M4" "$tool: transform.sh mentions reflector path '$a'"
125
+ else
126
+ fail "M4" "$tool: reflector path '$a' NOT found in $transform"
127
+ fi
128
+ ;;
129
+ LEGACY)
130
+ # M5 — legacy path at least mentioned in code (awareness, not proof of handling)
131
+ if grep -qF -- "$a" "$transform" || grep -qF -- "$a" "$VALIDATOR"; then
132
+ ok "M5" "$tool: legacy path '$a' mentioned in code"
133
+ else
134
+ fail "M5" "$tool: legacy path '$a' not mentioned in $transform or $VALIDATOR"
135
+ fi
136
+ ;;
137
+ FIELD)
138
+ case "$a" in
139
+ tier) tier="$b" ;;
140
+ display_name) display="$b" ;;
141
+ live_status) live_status="$b" ;;
142
+ live_date) live_date="$b" ;;
143
+ headless_command) headless="$b" ;;
144
+ ala_carte) ala_carte="$b" ;;
145
+ tool)
146
+ [ "$b" = "$tool" ] || fail "M1" "$meta: tool field '$b' != directory '$tool'"
147
+ ;;
148
+ esac
149
+ ;;
150
+ esac
151
+ done <<< "$flat"
152
+
153
+ # M6 — verified => dated + the date co-appears with the tool name in the live doc
154
+ # (fixed-string greps; no user data interpolated into a regex)
155
+ if [ "$live_status" = "verified" ]; then
156
+ if [ -z "$live_date" ]; then
157
+ fail "M6" "$tool: live_verification.status=verified but date is empty"
158
+ elif { grep -iF -- "$display" "$LIVE_DOC"; grep -iF -- "$tool" "$LIVE_DOC"; } | grep -qF -- "$live_date"; then
159
+ ok "M6" "$tool: live verification date $live_date matches $LIVE_DOC"
160
+ else
161
+ fail "M6" "$tool: verified date '$live_date' not found next to '$display' in $LIVE_DOC"
162
+ fi
163
+ else
164
+ ok "M6" "$tool: live status '$live_status' (no date assertion)"
165
+ fi
166
+
167
+ # M7 — headless CLI must be a word in the runner's `for c in ...` auto-detect list
168
+ # (strict: comments/usage text do NOT count; strip the trailing `; do`)
169
+ detect_line="$(grep -E 'for c in ' "$RUNNER" | head -1 | sed 's/;.*$//')"
170
+ if [ -z "$detect_line" ]; then
171
+ fail "M7" "no 'for c in' auto-detect line found in $RUNNER"
172
+ elif printf '%s\n' "$detect_line" | grep -qE "(^| )${headless}( |\$)"; then
173
+ ok "M7" "$tool: headless CLI '$headless' in $RUNNER auto-detect list"
174
+ else
175
+ fail "M7" "$tool: headless CLI '$headless' NOT in $RUNNER auto-detect list: $detect_line"
176
+ fi
177
+
178
+ # M9 — à-la-carte strategy matches the code (block ⇔ marker machinery present)
179
+ if [ "$ala_carte" = "block" ]; then
180
+ if grep -qF 'conductor:block' "$transform"; then
181
+ ok "M9" "$tool: ala_carte=block matches conductor:block machinery in transform.sh"
182
+ else
183
+ fail "M9" "$tool: metadata says ala_carte=block but $transform has no conductor:block machinery"
184
+ fi
185
+ else
186
+ if grep -qF 'conductor:block' "$transform"; then
187
+ fail "M9" "$tool: metadata says ala_carte=per-file but $transform contains conductor:block machinery"
188
+ else
189
+ ok "M9" "$tool: ala_carte=per-file (no block machinery, as declared)"
190
+ fi
191
+ fi
192
+
193
+ # M8 — the tier-assignment TABLE ROW must name this adapter
194
+ tier_row="$(grep -E "^\| \*\*${tier} — " "$MATRIX_DOC" | head -1)"
195
+ if [ -z "$tier_row" ]; then
196
+ fail "M8" "$tool: no tier table row '| **${tier} — ' in $MATRIX_DOC"
197
+ elif printf '%s' "$tier_row" | grep -qF -- "$display"; then
198
+ ok "M8" "$tool: matrix tier row ${tier} names ${display}"
199
+ else
200
+ fail "M8" "$tool: matrix tier row ${tier} does not name '${display}': $tier_row"
201
+ fi
202
+ done
203
+
204
+ echo
205
+ if [ "$FAIL" -eq 0 ]; then
206
+ echo "OK — adapter metadata is consistent with transform.sh / validator / live-verification doc / matrix."
207
+ exit 0
208
+ fi
209
+ echo "FAIL — adapter metadata inconsistency. metadata.json is the single source (ADR-040):"
210
+ echo "fix the code/doc to match it, or update metadata.json if reality changed."
211
+ exit 1
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env bash
2
+ # CONDUCTOR stale-token + version-stamp check (ADR-039).
3
+ # Exit 0 = clean, 1 = stale claim or stamp drift found, 2 = checker error.
4
+ #
5
+ # Two check classes:
6
+ # A. Version stamps — mechanizes the R7 release checklist items that drifted twice:
7
+ # A1: README.md has EXACTLY ONE status line, and it stamps the CURRENT
8
+ # package.json version (policy: re-stamped on EVERY release, patches
9
+ # included — ADR-039).
10
+ # A2: CHANGELOG.md has a section for the current version.
11
+ # B. Stale-claim tokens — known-false claims (data-driven from tools/stale-tokens.txt).
12
+ # A line matching a token fails UNLESS its CONTENT (not its file path) matches the
13
+ # token's allow_regex (legacy/historical qualifiers) or carries an inline waiver
14
+ # `stale-ok: <why>`.
15
+ #
16
+ # Scanned paths (living public surface — what an adopter reads as CURRENT truth):
17
+ # README.md / VISION.md / ROADMAP.md
18
+ # docs/*.md (top level only)
19
+ # core/ (all rule/template text)
20
+ # adapters/ (READMEs, specs, transform.sh emitted text, templates)
21
+ #
22
+ # Excluded (frozen history / private / machine data / self):
23
+ # CHANGELOG.md, docs/DESIGN-DECISIONS.md (ADRs), docs/audits|plans|specs|data/,
24
+ # docs/KPI.md, docs/GO-TO-MARKET.md, docs/LAUNCH-*.md, docs/CONDUCTOR-V0.2-DESIGN.md,
25
+ # CLAUDE.md / CURRENT_WORK.md / SESSION_HANDOFF.md (private session docs),
26
+ # adapters/*/metadata.json (machine data — its legacy_paths MUST name legacy tokens;
27
+ # checked by tools/check-adapter-metadata.sh instead), archive/, tools/ (this checker
28
+ # + its data file), scripts/, phase-2/ (frozen scaffold).
29
+ #
30
+ # See docs/DESIGN-DECISIONS.md ADR-026 (frozen-history carve-outs) + ADR-039 (this policy).
31
+
32
+ set -u
33
+
34
+ cd "$(dirname "$0")/.." || exit 2
35
+
36
+ TOKENS_FILE="tools/stale-tokens.txt"
37
+ [ -f "$TOKENS_FILE" ] || { echo "ERROR: $TOKENS_FILE not found" >&2; exit 2; }
38
+
39
+ FAIL=0
40
+
41
+ # --------------------------------------------------------------------------
42
+ # Class A — version stamps
43
+ # --------------------------------------------------------------------------
44
+ PKG_VERSION="$(sed -n -E 's/^[[:space:]]*"version"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/p' package.json 2>/dev/null | head -n 1)"
45
+ if [ -z "$PKG_VERSION" ]; then
46
+ echo "ERROR: cannot read version from package.json" >&2
47
+ exit 2
48
+ fi
49
+
50
+ STATUS_LINES="$(grep -cE '^> \*\*Status' README.md || true)"
51
+ if [ "$STATUS_LINES" -ne 1 ]; then
52
+ echo "FAIL[A1] README.md must have exactly ONE '> **Status' line (found ${STATUS_LINES}) — a leftover stamp from a previous release is itself drift (ADR-039)"
53
+ grep -nE '^> \*\*Status' README.md | head -4
54
+ FAIL=1
55
+ elif grep -E '^> \*\*Status' README.md | grep -qF "v${PKG_VERSION}"; then
56
+ echo "OK [A1] README.md status line stamps v${PKG_VERSION}"
57
+ else
58
+ echo "FAIL[A1] README.md status line does not stamp v${PKG_VERSION} (package.json is the single source; re-stamp on EVERY release, patches included — ADR-039)"
59
+ grep -nE '^> \*\*Status' README.md | head -2
60
+ FAIL=1
61
+ fi
62
+
63
+ if grep -qF "## [${PKG_VERSION}]" CHANGELOG.md; then
64
+ echo "OK [A2] CHANGELOG.md has a [${PKG_VERSION}] section"
65
+ else
66
+ echo "FAIL[A2] CHANGELOG.md has no section for [${PKG_VERSION}] (R7 checklist: [Unreleased] -> [${PKG_VERSION}] — <date>)"
67
+ FAIL=1
68
+ fi
69
+
70
+ # A3 (advisory, never fails the check): npm registry lag. The docs say "published to
71
+ # npm"; if the registry is behind package.json, `npm publish` is the missing R7 step.
72
+ # Skipped silently when npm/network is unavailable (CI-safe).
73
+ if command -v npm >/dev/null 2>&1; then
74
+ REG_VERSION="$(npm view omniconductor version 2>/dev/null || true)"
75
+ if [ -n "$REG_VERSION" ] && [ "$REG_VERSION" != "$PKG_VERSION" ]; then
76
+ echo "WARN[A3] npm registry serves omniconductor@${REG_VERSION} but package.json is ${PKG_VERSION} — run \`npm publish\` (advisory only)"
77
+ fi
78
+ fi
79
+
80
+ # --------------------------------------------------------------------------
81
+ # Class B — stale-claim tokens
82
+ # --------------------------------------------------------------------------
83
+ # Build the scan file list.
84
+ SCAN_FILES=()
85
+ for f in README.md VISION.md ROADMAP.md; do
86
+ [ -f "$f" ] && SCAN_FILES+=("$f")
87
+ done
88
+ while IFS= read -r f; do
89
+ case "$(basename "$f")" in
90
+ DESIGN-DECISIONS.md|KPI.md|GO-TO-MARKET.md|CONDUCTOR-V0.2-DESIGN.md|LAUNCH-*.md) continue ;;
91
+ esac
92
+ SCAN_FILES+=("$f")
93
+ done < <(find docs -maxdepth 1 -type f -name '*.md' | sort)
94
+ while IFS= read -r f; do
95
+ SCAN_FILES+=("$f")
96
+ done < <(find core adapters -type f \( -name '*.md' -o -name '*.sh' -o -name '*.template' -o -name '*.json' \) ! -name 'metadata.json' | sort)
97
+
98
+ TOTAL_HITS=0
99
+ while IFS=$'\t' read -r pattern reason hint allow_regex || [ -n "${pattern:-}" ]; do
100
+ # Strip CR from every field (CRLF-saved data file must not corrupt the last column).
101
+ pattern="${pattern%$'\r'}"; reason="${reason-}"; hint="${hint-}"; allow_regex="${allow_regex-}"
102
+ reason="${reason%$'\r'}"; hint="${hint%$'\r'}"; allow_regex="${allow_regex%$'\r'}"
103
+ case "$pattern" in ''|'#'*) continue ;; esac
104
+ matches="$(grep -FIn -- "$pattern" "${SCAN_FILES[@]}" 2>/dev/null | grep -v 'stale-ok:' || true)"
105
+ if [ -n "$matches" ] && [ -n "$allow_regex" ]; then
106
+ # Apply allow_regex to the line CONTENT only — never to the "file:line:" prefix,
107
+ # so a file path containing e.g. "legacy" cannot waive hits inside it.
108
+ matches="$(printf '%s\n' "$matches" | grep -Ev -- "^[^:]*:[0-9]+:.*(${allow_regex})" || true)"
109
+ fi
110
+ if [ -n "$matches" ]; then
111
+ count="$(printf '%s\n' "$matches" | wc -l | tr -d ' ')"
112
+ echo "FAIL[B] stale claim '$pattern' — $count line(s)"
113
+ echo " why stale : $reason"
114
+ echo " fix : $hint"
115
+ printf '%s\n' "$matches" | head -8 | sed 's/^/ /'
116
+ echo
117
+ FAIL=1
118
+ TOTAL_HITS=$((TOTAL_HITS + count))
119
+ fi
120
+ done < "$TOKENS_FILE"
121
+
122
+ if [ "$FAIL" -eq 0 ]; then
123
+ echo "OK — no stale claims on living surfaces; version stamps consistent (v${PKG_VERSION})."
124
+ exit 0
125
+ fi
126
+
127
+ echo "FAIL — stale-token check found problems (stale lines: ${TOTAL_HITS})."
128
+ echo "Fix the claim, add a legacy qualifier matching the token's allow_regex,"
129
+ echo "or (rarely) add an inline 'stale-ok: <why>' waiver. Policy: ADR-039."
130
+ exit 1