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.
- package/CHANGELOG.md +63 -0
- package/README.md +16 -12
- package/VISION.md +2 -2
- package/adapters/README.md +8 -8
- package/adapters/claude/README.md +2 -5
- package/adapters/claude/SUPPORTED-FEATURES.md +1 -1
- package/adapters/claude/metadata.json +39 -0
- package/adapters/claude/transform.sh +198 -34
- 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 +282 -35
- package/adapters/copilot/README.md +26 -29
- package/adapters/copilot/metadata.json +36 -0
- package/adapters/copilot/transform.sh +124 -33
- package/adapters/cursor/README.md +31 -30
- package/adapters/cursor/metadata.json +35 -0
- package/adapters/cursor/transform.sh +115 -26
- package/adapters/gemini/README.md +14 -15
- package/adapters/gemini/metadata.json +36 -0
- package/adapters/gemini/transform.sh +310 -34
- package/adapters/windsurf/README.md +20 -19
- package/adapters/windsurf/metadata.json +36 -0
- package/adapters/windsurf/transform.sh +137 -53
- 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/recipes/README.md +2 -2
- 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
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* CONDUCTOR — doc generator from adapter metadata (ADR-042; ADR-040 slice 2).
|
|
6
|
+
*
|
|
7
|
+
* Reads adapters/<tool>/metadata.json (the single source for enumerable adapter
|
|
8
|
+
* facts) and rewrites MARKED REGIONS in the docs below. Hand-editing inside a
|
|
9
|
+
* marked region is futile — edit metadata.json and re-run this script.
|
|
10
|
+
*
|
|
11
|
+
* Regions:
|
|
12
|
+
* docs/ADAPTER-LIVE-VERIFICATION.md <!-- generated:live-verification-table -->
|
|
13
|
+
* docs/COMPATIBILITY-MATRIX.md <!-- generated:adapter-outputs-table -->
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* node tools/generate-adapter-docs.js # rewrite regions in place
|
|
17
|
+
* node tools/generate-adapter-docs.js --check # exit 1 if any region is out of date (CI)
|
|
18
|
+
*
|
|
19
|
+
* Exit codes: 0 = up to date / written, 1 = --check found drift, 2 = error.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const fs = require('fs');
|
|
23
|
+
const path = require('path');
|
|
24
|
+
|
|
25
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
26
|
+
const TOOLS = ['claude', 'cursor', 'copilot', 'gemini', 'codex', 'windsurf'];
|
|
27
|
+
const CHECK = process.argv.includes('--check');
|
|
28
|
+
|
|
29
|
+
function die(msg) { process.stderr.write(`generate-adapter-docs: ${msg}\n`); process.exit(2); }
|
|
30
|
+
|
|
31
|
+
function loadMetadata() {
|
|
32
|
+
return TOOLS.map((tool) => {
|
|
33
|
+
const p = path.join(ROOT, 'adapters', tool, 'metadata.json');
|
|
34
|
+
let m;
|
|
35
|
+
try { m = JSON.parse(fs.readFileSync(p, 'utf8')); }
|
|
36
|
+
catch (e) { die(`${p}: ${e.message}`); }
|
|
37
|
+
if (m.tool !== tool) die(`${p}: tool field '${m.tool}' != directory '${tool}'`);
|
|
38
|
+
return m;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ---- renderers -------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
function liveCell(m) {
|
|
45
|
+
const lv = m.live_verification;
|
|
46
|
+
if (lv.status === 'verified') {
|
|
47
|
+
return `✅ **live-verified ${lv.date}** — ${lv.cli}${lv.note ? ` ${lv.note}` : ''}`;
|
|
48
|
+
}
|
|
49
|
+
return `🧪 ${lv.note || 'not yet run'}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function renderLiveVerificationTable(metas) {
|
|
53
|
+
const rows = metas.map((m) => `| ${m.display_name} | ✅ | ${liveCell(m)} |`);
|
|
54
|
+
return [
|
|
55
|
+
'| Adapter | File emission | Live rule-loading |',
|
|
56
|
+
'|---|---|---|',
|
|
57
|
+
...rows,
|
|
58
|
+
].join('\n');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function renderOutputsTable(metas) {
|
|
62
|
+
const rows = metas.map((m) => {
|
|
63
|
+
const outputs = m.outputs.map((o) => `\`${o.path}\``).join(' + ');
|
|
64
|
+
// "(legacy)" qualifier keeps these rows compliant with the stale-token
|
|
65
|
+
// allow_regex (ADR-039) — a bare legacy path here would read as a current claim.
|
|
66
|
+
const legacy = m.legacy_paths.length ? m.legacy_paths.map((l) => `\`${l}\` (legacy)`).join(', ') : '—';
|
|
67
|
+
const live = m.live_verification.status === 'verified'
|
|
68
|
+
? `✅ ${m.live_verification.date}`
|
|
69
|
+
: '🧪 pending';
|
|
70
|
+
const headless = `\`${m.headless_cli.invocation}\``;
|
|
71
|
+
const alaCarte = m.install && m.install.ala_carte === 'block' ? 'marked block' : 'per-file';
|
|
72
|
+
return `| ${m.display_name} | ${m.tier} | ${outputs} | ${legacy} | ${live} | ${headless} | ${alaCarte} |`;
|
|
73
|
+
});
|
|
74
|
+
return [
|
|
75
|
+
'| Tool | Tier | Emitted outputs | Legacy paths (still read) | Live-verified | Headless CLI | À la carte (`--mode`) |',
|
|
76
|
+
'|---|---|---|---|---|---|---|',
|
|
77
|
+
...rows,
|
|
78
|
+
].join('\n');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ---- region splicing -------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
function spliceRegion(file, name, body) {
|
|
84
|
+
const p = path.join(ROOT, file);
|
|
85
|
+
const src = fs.readFileSync(p, 'utf8');
|
|
86
|
+
const open = `<!-- generated:${name} — edit adapters/*/metadata.json + run tools/generate-adapter-docs.js; do not hand-edit (ADR-042) -->`;
|
|
87
|
+
const close = `<!-- /generated:${name} -->`;
|
|
88
|
+
const start = src.indexOf(open);
|
|
89
|
+
const end = src.indexOf(close);
|
|
90
|
+
if (start === -1 || end === -1 || end < start) {
|
|
91
|
+
die(`${file}: marked region '${name}' not found (need both open + close markers)`);
|
|
92
|
+
}
|
|
93
|
+
const next = src.slice(0, start + open.length) + '\n' + body + '\n' + src.slice(end);
|
|
94
|
+
return { p, src, next, changed: next !== src };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function main() {
|
|
98
|
+
const metas = loadMetadata();
|
|
99
|
+
const jobs = [
|
|
100
|
+
spliceRegion('docs/ADAPTER-LIVE-VERIFICATION.md', 'live-verification-table', renderLiveVerificationTable(metas)),
|
|
101
|
+
spliceRegion('docs/COMPATIBILITY-MATRIX.md', 'adapter-outputs-table', renderOutputsTable(metas)),
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
const drifted = jobs.filter((j) => j.changed);
|
|
105
|
+
if (CHECK) {
|
|
106
|
+
if (drifted.length) {
|
|
107
|
+
for (const j of drifted) {
|
|
108
|
+
process.stderr.write(`DRIFT: ${path.relative(ROOT, j.p)} generated region is out of date with adapters/*/metadata.json\n`);
|
|
109
|
+
}
|
|
110
|
+
process.stderr.write(`Run: node tools/generate-adapter-docs.js\n`);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
process.stdout.write('OK — generated doc regions match adapter metadata.\n');
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
for (const j of jobs) {
|
|
118
|
+
if (j.changed) { fs.writeFileSync(j.p, j.next); process.stdout.write(`wrote ${path.relative(ROOT, j.p)}\n`); }
|
|
119
|
+
else process.stdout.write(`up-to-date ${path.relative(ROOT, j.p)}\n`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
main();
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# CONDUCTOR live-verify — automated live rule-loading verification (ADR-043).
|
|
3
|
+
# Exit 0 = every attempted tool passed (skips don't fail), 1 = a probe FAILED, 2 = error.
|
|
4
|
+
#
|
|
5
|
+
# For each adapter (or --tool=<t>):
|
|
6
|
+
# 1. read adapters/<tool>/metadata.json → headless CLI command/invocation
|
|
7
|
+
# 2. if the CLI is not on PATH → SKIP (honest — never fake a ✅)
|
|
8
|
+
# 3. install the adapter into a throwaway temp dir (base install, no recipes)
|
|
9
|
+
# 4. run the tool headlessly with the probe prompt from docs/ADAPTER-LIVE-VERIFICATION.md
|
|
10
|
+
# 5. grade DETERMINISTICALLY (no LLM judge): the answer must name ≥3 of the 5
|
|
11
|
+
# universal rules AND mention CURRENT_WORK
|
|
12
|
+
# 6. on PASS: write live_verification {status:verified, date, cli} into the tool's
|
|
13
|
+
# metadata.json and regenerate the doc tables (tools/generate-adapter-docs.js)
|
|
14
|
+
#
|
|
15
|
+
# Freshness: any 'verified' date older than 90 days prints a WARN (re-verify).
|
|
16
|
+
#
|
|
17
|
+
# Usage:
|
|
18
|
+
# bash tools/live-verify.sh # all six (installed CLIs only)
|
|
19
|
+
# bash tools/live-verify.sh --tool=codex # one tool
|
|
20
|
+
# bash tools/live-verify.sh --dry-run # show plan, run nothing, write nothing
|
|
21
|
+
#
|
|
22
|
+
# Env: CONDUCTOR_LIVE_TIMEOUT (seconds per probe, default 300)
|
|
23
|
+
#
|
|
24
|
+
# Local-first by design: CI cannot run six authenticated AI CLIs (see
|
|
25
|
+
# docs/ADAPTER-LIVE-VERIFICATION.md "Why this is separate from CI").
|
|
26
|
+
|
|
27
|
+
set -u
|
|
28
|
+
|
|
29
|
+
cd "$(dirname "$0")/.." || exit 2
|
|
30
|
+
ROOT="$(pwd)"
|
|
31
|
+
|
|
32
|
+
command -v node >/dev/null 2>&1 || { echo "ERROR: node is required" >&2; exit 2; }
|
|
33
|
+
|
|
34
|
+
ONLY_TOOL=""
|
|
35
|
+
DRY_RUN="false"
|
|
36
|
+
for a in "$@"; do
|
|
37
|
+
case "$a" in
|
|
38
|
+
--tool=*) ONLY_TOOL="${a#--tool=}" ;;
|
|
39
|
+
--dry-run) DRY_RUN="true" ;;
|
|
40
|
+
*) echo "ERROR: unknown arg '$a' (use --tool=<t> / --dry-run)" >&2; exit 2 ;;
|
|
41
|
+
esac
|
|
42
|
+
done
|
|
43
|
+
|
|
44
|
+
TOOLS="claude cursor copilot gemini codex windsurf"
|
|
45
|
+
TIMEOUT_S="${CONDUCTOR_LIVE_TIMEOUT:-300}"
|
|
46
|
+
|
|
47
|
+
if [ -n "$ONLY_TOOL" ]; then
|
|
48
|
+
case " $TOOLS " in
|
|
49
|
+
*" $ONLY_TOOL "*) : ;;
|
|
50
|
+
*) echo "ERROR: unknown --tool '$ONLY_TOOL' (one of: $TOOLS)" >&2; exit 2 ;;
|
|
51
|
+
esac
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
PROBE="What workflow and rules are you operating under in this project? List the universal rules you can see, and tell me the first thing you must do before writing code."
|
|
55
|
+
|
|
56
|
+
FAILED=0
|
|
57
|
+
ATTEMPTED=0
|
|
58
|
+
PASSED=0
|
|
59
|
+
|
|
60
|
+
meta_field() { # meta_field <tool> <js-expr on m>
|
|
61
|
+
node -e "const m=require('$ROOT/adapters/$1/metadata.json');console.log($2 ?? '')"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
grade() { # grade <output-file> → prints "rule_hits current_work(0/1)"
|
|
65
|
+
local f="$1" hits=0 cw=0 r
|
|
66
|
+
for r in workflow spec-as-you-go quality-gates operations meta-discipline; do
|
|
67
|
+
grep -qi -- "$r" "$f" && hits=$((hits + 1))
|
|
68
|
+
done
|
|
69
|
+
grep -qi 'CURRENT_WORK' "$f" && cw=1
|
|
70
|
+
echo "$hits $cw"
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
run_with_timeout() { # run_with_timeout <seconds> <cmd...> (portable: no coreutils timeout)
|
|
74
|
+
local secs="$1"; shift
|
|
75
|
+
"$@" &
|
|
76
|
+
local pid=$!
|
|
77
|
+
local waited=0
|
|
78
|
+
while kill -0 "$pid" 2>/dev/null; do
|
|
79
|
+
if [ "$waited" -ge "$secs" ]; then
|
|
80
|
+
kill "$pid" 2>/dev/null
|
|
81
|
+
wait "$pid" 2>/dev/null
|
|
82
|
+
return 124
|
|
83
|
+
fi
|
|
84
|
+
sleep 2; waited=$((waited + 2))
|
|
85
|
+
done
|
|
86
|
+
wait "$pid"
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for tool in $TOOLS; do
|
|
90
|
+
[ -n "$ONLY_TOOL" ] && [ "$tool" != "$ONLY_TOOL" ] && continue
|
|
91
|
+
|
|
92
|
+
cmd="$(meta_field "$tool" 'm.headless_cli.command')"
|
|
93
|
+
status="$(meta_field "$tool" 'm.live_verification.status')"
|
|
94
|
+
date_v="$(meta_field "$tool" 'm.live_verification.date')"
|
|
95
|
+
|
|
96
|
+
if ! command -v "$cmd" >/dev/null 2>&1; then
|
|
97
|
+
echo "SKIP $tool — headless CLI '$cmd' not on PATH (honest skip; install it to verify)"
|
|
98
|
+
continue
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
if [ "$DRY_RUN" = "true" ]; then
|
|
102
|
+
echo "PLAN $tool — would install to a temp dir and probe via '$cmd' (current: $status${date_v:+ $date_v})"
|
|
103
|
+
continue
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
ATTEMPTED=$((ATTEMPTED + 1))
|
|
107
|
+
tmp="$(mktemp -d "${TMPDIR:-/tmp}/conductor-live-$tool.XXXXXX")" || { echo "ERROR: mktemp failed" >&2; exit 2; }
|
|
108
|
+
out="$tmp/.probe-output.txt"
|
|
109
|
+
|
|
110
|
+
echo "RUN $tool — installing into $tmp"
|
|
111
|
+
if ! bash "adapters/$tool/transform.sh" "$tmp" --no-prompt >/dev/null 2>&1; then
|
|
112
|
+
echo "FAIL $tool — adapter install failed (pre-probe)"; FAILED=1; rm -rf "$tmp"; continue
|
|
113
|
+
fi
|
|
114
|
+
( cd "$tmp" && git init -q ) 2>/dev/null || true
|
|
115
|
+
|
|
116
|
+
echo "RUN $tool — probing '$cmd' (timeout ${TIMEOUT_S}s)"
|
|
117
|
+
(
|
|
118
|
+
cd "$tmp" || exit 2
|
|
119
|
+
# Read-only probe; flags mirror core/reflector/run-weekly.sh (verified 2026-07-05),
|
|
120
|
+
# minus write permissions — the probe only asks a question.
|
|
121
|
+
case "$cmd" in
|
|
122
|
+
claude) run_with_timeout "$TIMEOUT_S" claude -p "$PROBE" ;;
|
|
123
|
+
codex) run_with_timeout "$TIMEOUT_S" codex exec --sandbox read-only "$PROBE" ;;
|
|
124
|
+
gemini) run_with_timeout "$TIMEOUT_S" gemini -p "$PROBE" ;;
|
|
125
|
+
cursor-agent) run_with_timeout "$TIMEOUT_S" cursor-agent -p "$PROBE" ;;
|
|
126
|
+
copilot) run_with_timeout "$TIMEOUT_S" copilot -p "$PROBE" ;;
|
|
127
|
+
devin) run_with_timeout "$TIMEOUT_S" devin -p "$PROBE" ;;
|
|
128
|
+
*) echo "unknown CLI '$cmd'" >&2; exit 2 ;;
|
|
129
|
+
esac
|
|
130
|
+
) > "$out" 2>&1
|
|
131
|
+
probe_rc=$?
|
|
132
|
+
|
|
133
|
+
if [ "$probe_rc" -eq 124 ]; then
|
|
134
|
+
echo "FAIL $tool — probe timed out after ${TIMEOUT_S}s"; FAILED=1; rm -rf "$tmp"; continue
|
|
135
|
+
fi
|
|
136
|
+
if [ "$probe_rc" -ne 0 ]; then
|
|
137
|
+
# A non-zero CLI exit (auth failure, transport error, crash) must never be
|
|
138
|
+
# recorded as verified — even if partial output happens to contain keywords.
|
|
139
|
+
echo "FAIL $tool — probe CLI exited $probe_rc (see $out)"; FAILED=1; continue
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
read -r hits cw <<EOF_GRADE
|
|
143
|
+
$(grade "$out")
|
|
144
|
+
EOF_GRADE
|
|
145
|
+
echo " $tool — graded: $hits/5 rule names, CURRENT_WORK=$cw (probe exit $probe_rc)"
|
|
146
|
+
|
|
147
|
+
if [ "$hits" -ge 3 ] && [ "$cw" -eq 1 ]; then
|
|
148
|
+
PASSED=$((PASSED + 1))
|
|
149
|
+
today="$(date +%F)"
|
|
150
|
+
cliver="$("$cmd" --version 2>/dev/null | head -n 1 | tr -d '\n' | cut -c1-60)"
|
|
151
|
+
[ -n "$cliver" ] || cliver="$cmd"
|
|
152
|
+
echo "PASS $tool — live-verified $today ($cliver)"
|
|
153
|
+
node -e '
|
|
154
|
+
const fs = require("fs");
|
|
155
|
+
const p = process.argv[1], date = process.argv[2], cli = process.argv[3], hits = process.argv[4];
|
|
156
|
+
const raw = fs.readFileSync(p, "utf8");
|
|
157
|
+
const m = JSON.parse(raw);
|
|
158
|
+
m.live_verification = { status: "verified", date, cli,
|
|
159
|
+
note: `headless probe listed ${hits}/5 rules + read-CURRENT_WORK-first` };
|
|
160
|
+
// Preserve the file style: compact one-line live_verification object.
|
|
161
|
+
const updated = raw.replace(/"live_verification": \{[^}]*\}/,
|
|
162
|
+
`"live_verification": { "status": "verified", "date": ${JSON.stringify(date)}, "cli": ${JSON.stringify(cli)}, "note": ${JSON.stringify(m.live_verification.note)} }`);
|
|
163
|
+
fs.writeFileSync(p, updated);
|
|
164
|
+
' "adapters/$tool/metadata.json" "$today" "$cliver" "$hits"
|
|
165
|
+
node tools/generate-adapter-docs.js >/dev/null
|
|
166
|
+
echo " $tool — metadata.json updated + doc tables regenerated"
|
|
167
|
+
else
|
|
168
|
+
echo "FAIL $tool — tool did not demonstrate loading the rules (see $out)"
|
|
169
|
+
echo " (an emission-verified adapter that fails live-loading usually means the tool's rules-file convention moved — see docs/ADAPTER-LIVE-VERIFICATION.md)"
|
|
170
|
+
FAILED=1
|
|
171
|
+
continue # keep $tmp for inspection on failure
|
|
172
|
+
fi
|
|
173
|
+
rm -rf "$tmp"
|
|
174
|
+
done
|
|
175
|
+
|
|
176
|
+
# Freshness guard — verified dates older than 90 days deserve a re-run.
|
|
177
|
+
node -e '
|
|
178
|
+
const fs = require("fs");
|
|
179
|
+
const tools = ["claude","cursor","copilot","gemini","codex","windsurf"];
|
|
180
|
+
const now = Date.now();
|
|
181
|
+
for (const t of tools) {
|
|
182
|
+
const m = JSON.parse(fs.readFileSync(`adapters/${t}/metadata.json`, "utf8"));
|
|
183
|
+
const lv = m.live_verification;
|
|
184
|
+
if (lv.status === "verified" && lv.date) {
|
|
185
|
+
const age = Math.floor((now - new Date(lv.date).getTime()) / 86400000);
|
|
186
|
+
if (age > 90) console.log(`WARN ${t} — live verification is ${age} days old (${lv.date}); re-run: bash tools/live-verify.sh --tool=${t}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
'
|
|
190
|
+
|
|
191
|
+
echo
|
|
192
|
+
if [ "$DRY_RUN" = "true" ]; then echo "dry-run complete — nothing executed or written."; exit 0; fi
|
|
193
|
+
echo "live-verify: attempted=$ATTEMPTED passed=$PASSED failed=$((FAILED))"
|
|
194
|
+
[ "$FAILED" -eq 0 ] || exit 1
|
|
195
|
+
exit 0
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Shared manifest-safety helpers for the six adapters.
|
|
3
|
+
#
|
|
4
|
+
# This file is sourced after an adapter defines TARGET_ABS, MANIFEST_PATH,
|
|
5
|
+
# DRY_RUN, and log(). It deliberately uses only bash + shasum/sha256sum so
|
|
6
|
+
# installers remain dependency-free.
|
|
7
|
+
|
|
8
|
+
conductor_sha256_file() {
|
|
9
|
+
local file="$1"
|
|
10
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
11
|
+
sha256sum "$file" | /usr/bin/awk '{print $1}'
|
|
12
|
+
else
|
|
13
|
+
/usr/bin/shasum -a 256 "$file" | /usr/bin/awk '{print $1}'
|
|
14
|
+
fi
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
# Return the one-line normal-file manifest entry for a relative path.
|
|
18
|
+
conductor_manifest_entry_for_path() {
|
|
19
|
+
local wanted="$1" line found
|
|
20
|
+
[ -f "$MANIFEST_PATH" ] || return 1
|
|
21
|
+
while IFS= read -r line; do
|
|
22
|
+
case "$line" in
|
|
23
|
+
*'"path":'*'"source":'*)
|
|
24
|
+
found="$(printf '%s' "$line" | /usr/bin/sed -E 's/.*"path": *"([^"]*)".*/\1/')"
|
|
25
|
+
[ "$found" = "$wanted" ] && { printf '%s\n' "$line"; return 0; }
|
|
26
|
+
;;
|
|
27
|
+
esac
|
|
28
|
+
done < "$MANIFEST_PATH"
|
|
29
|
+
return 1
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
# Return a simple string-valued JSON field from a manifest entry.
|
|
33
|
+
conductor_manifest_field() {
|
|
34
|
+
local line="$1" key="$2"
|
|
35
|
+
case "$line" in
|
|
36
|
+
*"\"$key\":"*)
|
|
37
|
+
printf '%s' "$line" | /usr/bin/sed -E "s/.*\"$key\": *\"([^\"]*)\".*/\\1/"
|
|
38
|
+
;;
|
|
39
|
+
*) return 1 ;;
|
|
40
|
+
esac
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
conductor_unique_backup_path() {
|
|
44
|
+
local dest="$1" ts candidate n=0
|
|
45
|
+
ts="$(/bin/date +%Y%m%d-%H%M%S)"
|
|
46
|
+
candidate="${dest}.conductor-backup-${ts}"
|
|
47
|
+
while [ -e "$candidate" ]; do
|
|
48
|
+
n=$((n + 1))
|
|
49
|
+
candidate="${dest}.conductor-backup-${ts}-${n}"
|
|
50
|
+
done
|
|
51
|
+
printf '%s' "$candidate"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Preserve the initial pre-CONDUCTOR backup across an unmodified re-install.
|
|
55
|
+
# If the prior emitted file was edited, back up that edit before replacing it.
|
|
56
|
+
conductor_manifest_backup_and_remember() {
|
|
57
|
+
local dest="$1" rel entry prior_sha prior_backup current_sha backup
|
|
58
|
+
MANIFEST_LAST_BACKUP=""
|
|
59
|
+
[ -f "$dest" ] || return 0
|
|
60
|
+
|
|
61
|
+
if [ "$DRY_RUN" = "true" ]; then
|
|
62
|
+
log "would safely back up existing $dest"
|
|
63
|
+
return 0
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
rel="${dest#$TARGET_ABS/}"
|
|
67
|
+
entry="$(conductor_manifest_entry_for_path "$rel" 2>/dev/null || true)"
|
|
68
|
+
if [ -n "$entry" ]; then
|
|
69
|
+
prior_sha="$(conductor_manifest_field "$entry" sha256 2>/dev/null || true)"
|
|
70
|
+
prior_backup="$(conductor_manifest_field "$entry" backup_path 2>/dev/null || true)"
|
|
71
|
+
current_sha="$(conductor_sha256_file "$dest")"
|
|
72
|
+
|
|
73
|
+
if [ -n "$prior_sha" ] && [ "$current_sha" = "$prior_sha" ]; then
|
|
74
|
+
if [ -n "$prior_backup" ] && [ -f "$TARGET_ABS/$prior_backup" ]; then
|
|
75
|
+
MANIFEST_LAST_BACKUP="$prior_backup"
|
|
76
|
+
log " retained original backup for $dest across re-install"
|
|
77
|
+
else
|
|
78
|
+
log " re-installing unchanged CONDUCTOR file $dest"
|
|
79
|
+
fi
|
|
80
|
+
return 0
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
if [ -n "$prior_sha" ]; then
|
|
84
|
+
log " preserved user-modified file before re-install: $dest"
|
|
85
|
+
else
|
|
86
|
+
log " preserved legacy manifest file before re-install: $dest"
|
|
87
|
+
fi
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
backup="$(conductor_unique_backup_path "$dest")"
|
|
91
|
+
/bin/cp "$dest" "$backup"
|
|
92
|
+
MANIFEST_LAST_BACKUP="${backup#$TARGET_ABS/}"
|
|
93
|
+
log " backed up existing $dest -> $backup"
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
conductor_manifest_file_matches() {
|
|
97
|
+
local file="$1" expected_sha="$2"
|
|
98
|
+
[ -n "$expected_sha" ] && [ -f "$file" ] \
|
|
99
|
+
&& [ "$(conductor_sha256_file "$file")" = "$expected_sha" ]
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
# Return the one-line block manifest entry matching a host relative path/name.
|
|
103
|
+
conductor_manifest_block_entry() {
|
|
104
|
+
local wanted_path="$1" wanted_name="$2" line found_path found_name
|
|
105
|
+
[ -f "$MANIFEST_PATH" ] || return 1
|
|
106
|
+
while IFS= read -r line; do
|
|
107
|
+
case "$line" in
|
|
108
|
+
*'"type": "block"'*)
|
|
109
|
+
found_path="$(printf '%s' "$line" | /usr/bin/sed -E 's/.*"path": *"([^"]*)".*/\1/')"
|
|
110
|
+
found_name="$(printf '%s' "$line" | /usr/bin/sed -E 's/.*"block": *"([^"]*)".*/\1/')"
|
|
111
|
+
[ "$found_path" = "$wanted_path" ] && [ "$found_name" = "$wanted_name" ] && {
|
|
112
|
+
printf '%s\n' "$line"; return 0;
|
|
113
|
+
}
|
|
114
|
+
;;
|
|
115
|
+
esac
|
|
116
|
+
done < "$MANIFEST_PATH"
|
|
117
|
+
return 1
|
|
118
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# CONDUCTOR stale-token list — data file for tools/check-stale-tokens.sh (ADR-039).
|
|
2
|
+
#
|
|
3
|
+
# Format: one rule per line, TAB-separated fields:
|
|
4
|
+
# pattern<TAB>reason<TAB>hint<TAB>allow_regex(optional)
|
|
5
|
+
#
|
|
6
|
+
# - pattern : fixed string (grep -F) that indicates a KNOWN-FALSE claim on living surfaces
|
|
7
|
+
# - reason : why the claim is false (which release/ADR falsified it)
|
|
8
|
+
# - hint : what to write instead
|
|
9
|
+
# - allow_regex : extended regex — a matching LINE is allowed (legacy/historical qualifiers)
|
|
10
|
+
#
|
|
11
|
+
# Waiver: a line containing `stale-ok: <why>` is always skipped.
|
|
12
|
+
# Process rule (R5 extension): an ADR that flips a fact adds the now-false claim here in the SAME PR.
|
|
13
|
+
# Scope: living public surfaces only — frozen history (CHANGELOG / ADRs / audits / plans / specs /
|
|
14
|
+
# archive / private session docs) is NOT scanned. See the checker header for the exact path set.
|
|
15
|
+
.codex/codex.md Codex adapter emits AGENTS.md (v0.6.1, ADR from 0.3.0 era) write AGENTS.md, or qualify as superseded/legacy supersed|early-design|legacy|historical
|
|
16
|
+
.windsurf/rules Windsurf target moved to .devin/rules/ in v0.3.0 (Devin Desktop rebrand) write .devin/rules/ (legacy .windsurf/rules/ still read) legacy|still read|\.devin
|
|
17
|
+
v0.2.0-foundation stale version label; versions are single-sourced from package.json reference package.json / CHANGELOG instead of stamping
|
|
18
|
+
no npx npm package `omniconductor` published since v0.4 remove; the npx CLI exists
|
|
19
|
+
no NPM package npm package `omniconductor` published since v0.4 remove; the npm package exists
|
|
20
|
+
not yet available all 6 adapters + the npx CLI shipped (CLI since v0.2) remove the "planned/not yet available" framing
|
|
21
|
+
until P2 adapter ships all 6 adapters shipped transform.sh in v0.2 drop the "until ... ships" framing
|
|
22
|
+
tools without an adapter all 6 tools have working adapters since v0.2/v0.3 rewrite: all 6 tools have adapters
|
|
23
|
+
❌ No sub-agent tools ship sub-agent dispatch natively (2026, ADR-031) use capability-vs-emission framing (ADR-031/034)
|
|
24
|
+
❌ No hooks tools ship hooks natively (2026, ADR-031; Windsurf lacks only session/stop events) use capability-vs-emission framing (ADR-031/034)
|
|
25
|
+
No hooks, no sub-agents tools ship both natively (2026, ADR-031) use capability-vs-emission framing (ADR-031/034)
|
|
26
|
+
no sub-agents/hooks tools ship both natively (2026, ADR-031) use capability-vs-emission framing (ADR-031/034)
|
|
27
|
+
No sub-agents, no hooks tools ship both natively (2026, ADR-031) use capability-vs-emission framing (ADR-031/034)
|
|
28
|
+
Single model per session all 6 tools support per-task model selection (ADR-031) write: manual per-task pick (tool-native); emission is Phase 2
|
|
29
|
+
Single model per Codex invocation Codex supports per-task model selection (ADR-031) write: manual per-task pick (tool-native)
|
|
30
|
+
Codex (T3) Codex tier corrected T3 → T2 in v0.7.0 (matrix is the tier source, ADR-040 M8) write Codex (T2)
|
|
31
|
+
Codex adapter (T3) Codex tier corrected T3 → T2 in v0.7.0 write T2
|
|
32
|
+
pre-1.0 (active v1.0.0 shipped 2026-07-09 — the pre-1.0 framing is falsified reference CHANGELOG for current version
|