omniconductor 0.5.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.
- package/CHANGELOG.md +68 -0
- package/README.md +59 -19
- package/VISION.md +2 -2
- package/adapters/README.md +8 -8
- package/adapters/claude/README.md +3 -6
- package/adapters/claude/SUPPORTED-FEATURES.md +4 -4
- package/adapters/claude/metadata.json +39 -0
- package/adapters/claude/transform-spec.md +1 -1
- package/adapters/claude/transform.sh +208 -38
- package/adapters/codex/README.md +23 -27
- package/adapters/codex/metadata.json +35 -0
- package/adapters/codex/transform-spec.md +5 -6
- package/adapters/codex/transform.sh +284 -37
- package/adapters/copilot/README.md +26 -29
- package/adapters/copilot/metadata.json +36 -0
- package/adapters/copilot/transform.sh +126 -35
- package/adapters/cursor/README.md +31 -30
- package/adapters/cursor/metadata.json +35 -0
- package/adapters/cursor/transform.sh +117 -28
- package/adapters/gemini/README.md +14 -15
- package/adapters/gemini/metadata.json +36 -0
- package/adapters/gemini/transform.sh +312 -36
- package/adapters/windsurf/README.md +20 -19
- package/adapters/windsurf/metadata.json +36 -0
- package/adapters/windsurf/transform.sh +139 -55
- package/bin/doctor.js +257 -0
- package/bin/omniconductor.js +15 -2
- package/core/anti-patterns/frequent-rule-file-edit.md +1 -1
- package/core/anti-patterns/single-monolithic-rule-file.md +1 -1
- package/core/hooks/README.md +1 -0
- package/core/hooks/pretool-loop-guard.sh.template +177 -0
- package/core/recipes/README.md +6 -4
- package/core/recipes/loop-engineering.md +73 -0
- package/core/universal-rules/README.md +4 -4
- package/core/universal-rules/meta-discipline.md +4 -4
- package/core/universal-rules/spec-as-you-go.md +1 -1
- package/docs/ADAPTER-LIVE-VERIFICATION.md +73 -0
- package/docs/COMPARISON.md +133 -0
- package/docs/COMPATIBILITY-MATRIX.md +126 -0
- package/docs/DESIGN-DECISIONS.md +1268 -0
- package/docs/MANUAL-INSTALL.md +15 -15
- package/docs/PUBLICATION-POLICY.md +46 -0
- package/docs/PUBLISH-GUIDE.md +141 -0
- package/package.json +7 -1
- package/tools/check-adapter-metadata.sh +211 -0
- package/tools/check-stale-tokens.sh +130 -0
- package/tools/generate-adapter-docs.js +123 -0
- package/tools/live-verify.sh +195 -0
- package/tools/manifest-safety.sh +118 -0
- package/tools/stale-tokens.txt +32 -0
- package/tools/test-install-modes.sh +277 -0
- package/tools/validate-adapter-output.sh +1 -1
package/docs/MANUAL-INSTALL.md
CHANGED
|
@@ -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
|
|
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
|
|
10
|
-
- **Manual file copy (fallback)** — explicit `cp` / `cat` commands and frontmatter cheat sheet.
|
|
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 | ✅ | `
|
|
34
|
-
| Cursor | ✅
|
|
35
|
-
| GitHub Copilot | ✅
|
|
36
|
-
| Gemini CLI |
|
|
37
|
-
| Codex (OpenAI) |
|
|
38
|
-
| Windsurf |
|
|
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
|
-
>
|
|
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
|
|
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
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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.
|
|
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
|