copperhead 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +87 -0
- package/dist/agent/filetools.js +118 -0
- package/dist/agent/filetools.js.map +1 -0
- package/dist/agent/ledger.js +43 -0
- package/dist/agent/ledger.js.map +1 -0
- package/dist/agent/loop.js +279 -0
- package/dist/agent/loop.js.map +1 -0
- package/dist/agent/prompts.js +47 -0
- package/dist/agent/prompts.js.map +1 -0
- package/dist/agent/providers/anthropic.js +78 -0
- package/dist/agent/providers/anthropic.js.map +1 -0
- package/dist/agent/providers/openai.js +74 -0
- package/dist/agent/providers/openai.js.map +1 -0
- package/dist/agent/tools.js +439 -0
- package/dist/agent/tools.js.map +1 -0
- package/dist/agent/transcript.js +60 -0
- package/dist/agent/transcript.js.map +1 -0
- package/dist/agent/types.js +2 -0
- package/dist/agent/types.js.map +1 -0
- package/dist/cli.js +185 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/check.js +64 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/create.js +91 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/sync.js +144 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/config.js +64 -0
- package/dist/config.js.map +1 -0
- package/dist/kicad/cli.js +91 -0
- package/dist/kicad/cli.js.map +1 -0
- package/dist/kicad/report.js +48 -0
- package/dist/kicad/report.js.map +1 -0
- package/dist/kicad/sexp.js +291 -0
- package/dist/kicad/sexp.js.map +1 -0
- package/dist/memory/constraints.js +43 -0
- package/dist/memory/constraints.js.map +1 -0
- package/dist/memory/drift.js +81 -0
- package/dist/memory/drift.js.map +1 -0
- package/dist/memory/scaffold.js +215 -0
- package/dist/memory/scaffold.js.map +1 -0
- package/dist/openspec/cli.js +31 -0
- package/dist/openspec/cli.js.map +1 -0
- package/dist/util/env.js +68 -0
- package/dist/util/env.js.map +1 -0
- package/dist/util/git.js +53 -0
- package/dist/util/git.js.map +1 -0
- package/dist/util/paths.js +25 -0
- package/dist/util/paths.js.map +1 -0
- package/dist/util/redact.js +21 -0
- package/dist/util/redact.js.map +1 -0
- package/dist/util/retry.js +27 -0
- package/dist/util/retry.js.map +1 -0
- package/package.json +73 -0
- package/src/agent/filetools.ts +136 -0
- package/src/agent/ledger.ts +71 -0
- package/src/agent/loop.ts +320 -0
- package/src/agent/prompts.ts +58 -0
- package/src/agent/providers/anthropic.ts +81 -0
- package/src/agent/providers/openai.ts +80 -0
- package/src/agent/tools.ts +481 -0
- package/src/agent/transcript.ts +78 -0
- package/src/agent/types.ts +32 -0
- package/src/cli.ts +188 -0
- package/src/commands/check.ts +85 -0
- package/src/commands/create.ts +125 -0
- package/src/commands/sync.ts +182 -0
- package/src/config.ts +77 -0
- package/src/kicad/cli.ts +106 -0
- package/src/kicad/report.ts +81 -0
- package/src/kicad/sexp.ts +327 -0
- package/src/memory/constraints.ts +73 -0
- package/src/memory/drift.ts +97 -0
- package/src/memory/scaffold.ts +241 -0
- package/src/openspec/cli.ts +43 -0
- package/src/util/env.ts +65 -0
- package/src/util/git.ts +63 -0
- package/src/util/paths.ts +25 -0
- package/src/util/redact.ts +20 -0
- package/src/util/retry.ts +33 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { createInterface } from 'node:readline/promises';
|
|
5
|
+
import { loadConfig, resolveModel } from './config.js';
|
|
6
|
+
import { runInit, InitError } from './memory/scaffold.js';
|
|
7
|
+
import { runCheck } from './commands/check.js';
|
|
8
|
+
import { syncVerify, syncResolve, formatSyncReport } from './commands/sync.js';
|
|
9
|
+
import { runCreate } from './commands/create.js';
|
|
10
|
+
import { runAgentLoop } from './agent/loop.js';
|
|
11
|
+
import { kicadCliVersion } from './kicad/cli.js';
|
|
12
|
+
import { loadEnvFile } from './util/env.js';
|
|
13
|
+
// Read .env from the working directory before any command resolves a model or a
|
|
14
|
+
// provider. Loaded here rather than per-command so `check` behaves identically,
|
|
15
|
+
// though check never reads a key: it stays LLM-free and network-free either way.
|
|
16
|
+
// A real environment variable always beats the file.
|
|
17
|
+
loadEnvFile(process.cwd());
|
|
18
|
+
const program = new Command();
|
|
19
|
+
const repoOf = (opts) => path.resolve(opts.repo ?? process.cwd());
|
|
20
|
+
async function confirmTty(question) {
|
|
21
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
22
|
+
const answer = await rl.question(`${question} [y/N] `);
|
|
23
|
+
rl.close();
|
|
24
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
25
|
+
}
|
|
26
|
+
program
|
|
27
|
+
.name('copperhead')
|
|
28
|
+
.description('Cursor for circuit boards: an AI agent for real KiCad repositories')
|
|
29
|
+
.version('0.1.0')
|
|
30
|
+
.option('--repo <path>', 'target repository (default: cwd)')
|
|
31
|
+
.option('--json', 'machine-readable output');
|
|
32
|
+
program
|
|
33
|
+
.command('init')
|
|
34
|
+
.description('scaffold docs/ from an existing schematic; idempotent')
|
|
35
|
+
.option('--path <dir>', 'where to look for KiCad files', '.')
|
|
36
|
+
.option('--force', 'overwrite hand-edited generated docs')
|
|
37
|
+
.option('--no-hooks', 'skip git pre-commit hook installation')
|
|
38
|
+
.action(async (opts) => {
|
|
39
|
+
const repo = repoOf(program.opts());
|
|
40
|
+
try {
|
|
41
|
+
await kicadCliVersion();
|
|
42
|
+
const res = await runInit({
|
|
43
|
+
repoRoot: repo,
|
|
44
|
+
searchPath: opts.path,
|
|
45
|
+
force: opts.force ?? false,
|
|
46
|
+
installHooks: opts.hooks,
|
|
47
|
+
});
|
|
48
|
+
if (program.opts().json) {
|
|
49
|
+
console.log(JSON.stringify(res, null, 2));
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
for (const f of res.created)
|
|
53
|
+
console.log(`created ${f}`);
|
|
54
|
+
for (const f of res.skipped)
|
|
55
|
+
console.log(`unchanged ${f}`);
|
|
56
|
+
for (const f of res.refused)
|
|
57
|
+
console.log(`REFUSED (hand-edited; use --force): ${f}`);
|
|
58
|
+
}
|
|
59
|
+
process.exit(res.refused.length ? 1 : 0);
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
console.error(err instanceof InitError ? err.message : err.message);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
const checkAction = async () => {
|
|
67
|
+
const repo = repoOf(program.opts());
|
|
68
|
+
const json = Boolean(program.opts().json);
|
|
69
|
+
try {
|
|
70
|
+
await kicadCliVersion();
|
|
71
|
+
const res = await runCheck(repo, json ? () => { } : (s) => console.log(s));
|
|
72
|
+
if (json)
|
|
73
|
+
console.log(JSON.stringify(res, null, 2));
|
|
74
|
+
process.exit(res.ok ? 0 : 1);
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
console.error(err.message);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
program
|
|
82
|
+
.command('check')
|
|
83
|
+
.alias('verify')
|
|
84
|
+
.description('ERC + DRC + doc-drift + spec validation; no LLM calls; CI-safe')
|
|
85
|
+
.action(checkAction);
|
|
86
|
+
program
|
|
87
|
+
.command('do')
|
|
88
|
+
.description('the core loop: propose, edit, verify, propagate, commit')
|
|
89
|
+
.argument('<request>', 'the change request in natural language')
|
|
90
|
+
.option('--model <model>', 'gpt-5 | claude (or a full model id)')
|
|
91
|
+
.option('--max-turns <n>', 'turn budget for this run')
|
|
92
|
+
.option('--allow-dirty', 'allow a dirty tree (snapshot via git stash create)')
|
|
93
|
+
.option('--dry-run', 'propose the diff, write nothing')
|
|
94
|
+
.option('--interactive', 'pause for approval after the proposal validates')
|
|
95
|
+
.action(async (request, opts) => {
|
|
96
|
+
const repo = repoOf(program.opts());
|
|
97
|
+
try {
|
|
98
|
+
await kicadCliVersion();
|
|
99
|
+
const config = await loadConfig(repo);
|
|
100
|
+
const model = resolveModel(opts.model, config);
|
|
101
|
+
const res = await runAgentLoop({
|
|
102
|
+
repoRoot: repo,
|
|
103
|
+
request,
|
|
104
|
+
model,
|
|
105
|
+
...(opts.maxTurns ? { maxTurns: parseInt(opts.maxTurns, 10) } : {}),
|
|
106
|
+
allowDirty: opts.allowDirty ?? false,
|
|
107
|
+
dryRun: opts.dryRun ?? false,
|
|
108
|
+
interactive: opts.interactive ?? false,
|
|
109
|
+
confirm: confirmTty,
|
|
110
|
+
});
|
|
111
|
+
if (program.opts().json)
|
|
112
|
+
console.log(JSON.stringify(res, null, 2));
|
|
113
|
+
process.exit(res.outcome === 'failure' ? 1 : 0);
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
console.error(err.message);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
program
|
|
121
|
+
.command('sync')
|
|
122
|
+
.description('verify the whole design state for inconsistencies and resolve drift')
|
|
123
|
+
.option('--model <model>', 'model for the resolve phase')
|
|
124
|
+
.option('--dry-run', 'print the inconsistency report, write nothing')
|
|
125
|
+
.action(async (opts) => {
|
|
126
|
+
const repo = repoOf(program.opts());
|
|
127
|
+
try {
|
|
128
|
+
await kicadCliVersion();
|
|
129
|
+
const report = await syncVerify(repo);
|
|
130
|
+
const json = Boolean(program.opts().json);
|
|
131
|
+
if (json)
|
|
132
|
+
console.log(JSON.stringify(report, null, 2));
|
|
133
|
+
else
|
|
134
|
+
console.log(formatSyncReport(report));
|
|
135
|
+
if (opts.dryRun) {
|
|
136
|
+
process.exit(report.violations.length ? 2 : 0);
|
|
137
|
+
}
|
|
138
|
+
if (report.violations.length) {
|
|
139
|
+
// requirement violations are never auto-resolved (AC-7.3)
|
|
140
|
+
process.exit(2);
|
|
141
|
+
}
|
|
142
|
+
if (!report.resolvable.length) {
|
|
143
|
+
process.exit(0);
|
|
144
|
+
}
|
|
145
|
+
const config = await loadConfig(repo);
|
|
146
|
+
const model = resolveModel(opts.model, config);
|
|
147
|
+
const res = await syncResolve(repo, report, model, json ? () => { } : (s) => console.log(s));
|
|
148
|
+
process.exit(res.ok ? 0 : 1);
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
console.error(err.message);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
program
|
|
156
|
+
.command('create')
|
|
157
|
+
.description('Mode A: full pipeline from a product brief to the output package')
|
|
158
|
+
.requiredOption('--brief <file>', 'product brief (markdown)')
|
|
159
|
+
.option('--model <model>', 'gpt-5 | claude')
|
|
160
|
+
.option('--interactive', 're-enable the human gates (spec approval, pre-export)')
|
|
161
|
+
.action(async (opts) => {
|
|
162
|
+
const repo = repoOf(program.opts());
|
|
163
|
+
try {
|
|
164
|
+
await kicadCliVersion();
|
|
165
|
+
const config = await loadConfig(repo);
|
|
166
|
+
const model = resolveModel(opts.model, config);
|
|
167
|
+
const res = await runCreate({
|
|
168
|
+
repoRoot: repo,
|
|
169
|
+
briefPath: opts.brief,
|
|
170
|
+
model,
|
|
171
|
+
interactive: opts.interactive ?? false,
|
|
172
|
+
log: (s) => console.log(s),
|
|
173
|
+
});
|
|
174
|
+
process.exit(res.ok ? 0 : 1);
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
console.error(err.message);
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
program.parseAsync().catch((err) => {
|
|
182
|
+
console.error(err.message);
|
|
183
|
+
process.exit(1);
|
|
184
|
+
});
|
|
185
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { loadConfig } from '../config.js';
|
|
4
|
+
import { runErc, runDrc } from '../kicad/cli.js';
|
|
5
|
+
import { formatViolations } from '../kicad/report.js';
|
|
6
|
+
import { checkDrift } from '../memory/drift.js';
|
|
7
|
+
import { loadConstraints, checkForbiddenPins } from '../memory/constraints.js';
|
|
8
|
+
import { pinNets } from '../kicad/sexp.js';
|
|
9
|
+
import { openspecValidate } from '../openspec/cli.js';
|
|
10
|
+
export async function runCheck(repoRoot, log) {
|
|
11
|
+
const config = await loadConfig(repoRoot);
|
|
12
|
+
let erc = null;
|
|
13
|
+
let drc = null;
|
|
14
|
+
if (config.schematic && existsSync(path.join(repoRoot, config.schematic))) {
|
|
15
|
+
erc = await runErc(path.join(repoRoot, config.schematic));
|
|
16
|
+
log(erc.ok ? 'ERC ✓' : formatViolations(erc));
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
log('ERC skipped (no schematic configured; run copperhead init)');
|
|
20
|
+
}
|
|
21
|
+
if (config.board && existsSync(path.join(repoRoot, config.board))) {
|
|
22
|
+
drc = await runDrc(path.join(repoRoot, config.board));
|
|
23
|
+
log(drc.ok ? 'DRC ✓' : formatViolations(drc));
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
log('DRC skipped (no board configured)');
|
|
27
|
+
}
|
|
28
|
+
let drift = [];
|
|
29
|
+
if (config.schematic && existsSync(path.join(repoRoot, config.schematic))) {
|
|
30
|
+
drift = await checkDrift(repoRoot, config.docs, config.schematic);
|
|
31
|
+
log(drift.length === 0 ? 'drift ✓' : drift.map((m) => `drift: ${m.doc} claims "${m.claim}" but actual is "${m.actual}"`).join('\n'));
|
|
32
|
+
}
|
|
33
|
+
let openspec = null;
|
|
34
|
+
if (existsSync(path.join(repoRoot, 'openspec', 'config.yaml'))) {
|
|
35
|
+
const res = await openspecValidate(repoRoot);
|
|
36
|
+
openspec = { ok: res.ok, detail: res.output };
|
|
37
|
+
log(res.ok ? 'openspec ✓' : `openspec: ${res.output}`);
|
|
38
|
+
}
|
|
39
|
+
let constraintViolations = [];
|
|
40
|
+
if (config.schematic && existsSync(path.join(repoRoot, config.schematic))) {
|
|
41
|
+
const registry = await loadConstraints(repoRoot);
|
|
42
|
+
const pins = await pinNets(path.join(repoRoot, config.schematic));
|
|
43
|
+
constraintViolations = checkForbiddenPins(registry, pins);
|
|
44
|
+
if (Object.keys(registry).length) {
|
|
45
|
+
log(constraintViolations.length === 0
|
|
46
|
+
? 'constraints ✓'
|
|
47
|
+
: constraintViolations.map((v) => `constraint ${v.key}: ${v.description} (source: ${v.source})`).join('\n'));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const ok = (erc?.ok ?? true) &&
|
|
51
|
+
(drc?.ok ?? true) &&
|
|
52
|
+
drift.length === 0 &&
|
|
53
|
+
(openspec?.ok ?? true) &&
|
|
54
|
+
constraintViolations.length === 0;
|
|
55
|
+
return {
|
|
56
|
+
ok,
|
|
57
|
+
erc: erc ? { ok: erc.ok, violations: erc.violations.length } : null,
|
|
58
|
+
drc: drc ? { ok: drc.ok, violations: drc.violations.length } : null,
|
|
59
|
+
drift: { ok: drift.length === 0, mismatches: drift },
|
|
60
|
+
openspec,
|
|
61
|
+
constraints: { ok: constraintViolations.length === 0, violations: constraintViolations },
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check.js","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { loadConfig } from '../config.js';
|
|
5
|
+
import { runAgentLoop } from '../agent/loop.js';
|
|
6
|
+
import { openspecInit } from '../openspec/cli.js';
|
|
7
|
+
import { runCheck } from './check.js';
|
|
8
|
+
const docExists = (repoRoot, rel) => existsSync(path.join(repoRoot, rel));
|
|
9
|
+
async function docHasContent(repoRoot, rel, marker) {
|
|
10
|
+
const p = path.join(repoRoot, rel);
|
|
11
|
+
if (!existsSync(p))
|
|
12
|
+
return false;
|
|
13
|
+
return (await readFile(p, 'utf8')).includes(marker);
|
|
14
|
+
}
|
|
15
|
+
export const STAGES = [
|
|
16
|
+
{
|
|
17
|
+
name: 'spec-seed',
|
|
18
|
+
isComplete: (root, docs) => docHasContent(root, path.join(docs, 'SPEC.md'), '## Budgets'),
|
|
19
|
+
prompt: (brief) => `Stage 1 of the create pipeline: seed the requirements. From the product brief below, write docs/SPEC.md (what the device is, top-level constraints and budgets). Every budget you state must also be recorded with record_constraint. Anything the brief does not state: propose a sensible default and flag it ASSUMED. If an openspec/ workspace exists, also seed openspec/specs/ with per-capability requirements using Given/When/Then scenarios.\n\nBrief:\n${brief}`,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'architecture',
|
|
23
|
+
isComplete: (root, docs) => docExists(root, path.join(docs, 'SUBSYSTEMS.md')),
|
|
24
|
+
prompt: () => 'Stage 2: architecture. Write docs/SUBSYSTEMS.md: the block diagram in prose, one section per subsystem (power, MCU, connectivity, UI, ...), with the reasoning and key values for each. Respect every budget in SPEC.md.',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'part-selection',
|
|
28
|
+
isComplete: (root, docs) => docExists(root, path.join(docs, 'BOM.md')),
|
|
29
|
+
prompt: () => 'Stage 3: part selection. Write docs/BOM.md with the fixed table format (| Refdes | Value | Footprint | MPN | Rationale |). Every MPN you introduce is flagged UNVERIFIED with a datasheet-verifiable justification. Check leakage/quiescent current of every part against the power budget. Run check_drift before finishing.',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'schematic',
|
|
33
|
+
isComplete: async (root) => {
|
|
34
|
+
const config = await loadConfig(root);
|
|
35
|
+
return !!config.schematic && existsSync(path.join(root, config.schematic));
|
|
36
|
+
},
|
|
37
|
+
prompt: () => 'Stage 4: schematic. Build the schematic sheet by sheet from BOM.md and SUBSYSTEMS.md. After each sheet, run run_erc and fix violations before moving on. Same net names and refdes everywhere. Update PINOUT.md as you assign pins; check the strapping table first.',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'layout-draft',
|
|
41
|
+
isComplete: (root, docs) => docHasContent(root, path.join(docs, 'LAYOUT.md'), '## Draft quality'),
|
|
42
|
+
prompt: () => 'Stage 5: first-draft layout. Rule-driven placement written as real coordinates: connectors on edges, decoupling at IC pins, ESD at connectors, keepouts honored. Route power and short critical nets; leave the rest as ratsnest. Every routed net must pass run_drc. Then write the "## Draft quality" section in LAYOUT.md: exactly what is fine and what a human or specialist tool should redo. Non-optimal is acceptable; unlabeled non-optimal is not.',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'outputs',
|
|
46
|
+
isComplete: (root) => existsSync(path.join(root, 'outputs')),
|
|
47
|
+
prompt: () => 'Stage 6: outputs package. Export into outputs/: gerbers+drill (JLC profile), DXF and STEP outline, SVG renders (export_svg), and an ordering BOM.csv generated from BOM.md (refdes, MPN, qty). Every export must succeed.',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'firmware',
|
|
51
|
+
isComplete: (root) => existsSync(path.join(root, 'firmware')),
|
|
52
|
+
prompt: () => 'Stage 7: firmware scaffold. Generate firmware/ for the chosen MCU HAL: pins.h generated from PINOUT.md (single source of truth), driver stubs, and one working happy path. If the vendor toolchain is available, the build must pass; if not, note "not compiled here" explicitly in DEVPLAN.md.',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'devplan',
|
|
56
|
+
isComplete: (root, docs) => docExists(root, path.join(docs, 'DEVPLAN.md')),
|
|
57
|
+
prompt: () => 'Stage 8: DEVPLAN.md. Write docs/DEVPLAN.md: bring-up steps in order, test points and what to meter first, risk list, and the prototype order plan.',
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
export async function runCreate(opts) {
|
|
61
|
+
const brief = await readFile(path.resolve(opts.briefPath), 'utf8');
|
|
62
|
+
const config = await loadConfig(opts.repoRoot);
|
|
63
|
+
await openspecInit(opts.repoRoot);
|
|
64
|
+
const completed = [];
|
|
65
|
+
for (const stage of STAGES) {
|
|
66
|
+
if (await stage.isComplete(opts.repoRoot, config.docs)) {
|
|
67
|
+
opts.log(`stage ${stage.name}: already complete (resuming past it)`);
|
|
68
|
+
completed.push(stage.name);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
opts.log(`stage ${stage.name}: running`);
|
|
72
|
+
const res = await runAgentLoop({
|
|
73
|
+
repoRoot: opts.repoRoot,
|
|
74
|
+
model: opts.model,
|
|
75
|
+
request: `create pipeline stage: ${stage.name}`,
|
|
76
|
+
stagePrompt: stage.prompt(brief),
|
|
77
|
+
interactive: opts.interactive ?? false,
|
|
78
|
+
allowDirty: true, // stages build on each other's uncommitted state within the pipeline
|
|
79
|
+
log: opts.log,
|
|
80
|
+
});
|
|
81
|
+
if (res.outcome !== 'success') {
|
|
82
|
+
opts.log(`stage ${stage.name} did not complete (${res.outcome}); re-run copperhead create to resume here`);
|
|
83
|
+
return { ok: false, completed };
|
|
84
|
+
}
|
|
85
|
+
completed.push(stage.name);
|
|
86
|
+
}
|
|
87
|
+
const check = await runCheck(opts.repoRoot, opts.log);
|
|
88
|
+
opts.log(check.ok ? 'create pipeline complete; all checks green' : 'create pipeline complete with check failures');
|
|
89
|
+
return { ok: check.ok, completed };
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=create.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create.js","sourceRoot":"","sources":["../../src/commands/create.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { loadConfig } from '../config.js';
|
|
5
|
+
import { checkDrift } from '../memory/drift.js';
|
|
6
|
+
import { loadConstraints, checkForbiddenPins } from '../memory/constraints.js';
|
|
7
|
+
import { pinNets } from '../kicad/sexp.js';
|
|
8
|
+
import { openspecValidate } from '../openspec/cli.js';
|
|
9
|
+
import { runAgentLoop } from '../agent/loop.js';
|
|
10
|
+
export async function syncVerify(repoRoot) {
|
|
11
|
+
const config = await loadConfig(repoRoot);
|
|
12
|
+
const resolvable = [];
|
|
13
|
+
const violations = [];
|
|
14
|
+
if (config.schematic && existsSync(path.join(repoRoot, config.schematic))) {
|
|
15
|
+
for (const m of await checkDrift(repoRoot, config.docs, config.schematic)) {
|
|
16
|
+
resolvable.push({
|
|
17
|
+
kind: 'drift',
|
|
18
|
+
doc: m.doc,
|
|
19
|
+
claim: m.claim,
|
|
20
|
+
actual: m.actual,
|
|
21
|
+
resolution: `update ${m.doc} to match the as-built schematic (KiCad files are truth for as-built facts)`,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// dual-write audit: every registry key should be mentioned in some doc, and
|
|
26
|
+
// every budget in config should exist in the registry
|
|
27
|
+
const registry = await loadConstraints(repoRoot);
|
|
28
|
+
const docsDir = path.join(repoRoot, config.docs);
|
|
29
|
+
let docsText = '';
|
|
30
|
+
for (const name of ['SPEC.md', 'BOM.md', 'PINOUT.md', 'SUBSYSTEMS.md', 'LAYOUT.md']) {
|
|
31
|
+
const p = path.join(docsDir, name);
|
|
32
|
+
if (existsSync(p))
|
|
33
|
+
docsText += `\n<<<${name}>>>\n` + (await readFile(p, 'utf8'));
|
|
34
|
+
}
|
|
35
|
+
for (const key of Object.keys(registry)) {
|
|
36
|
+
const shortKey = key.split('.').pop();
|
|
37
|
+
if (docsText && !docsText.includes(shortKey)) {
|
|
38
|
+
resolvable.push({
|
|
39
|
+
kind: 'dual-write',
|
|
40
|
+
doc: 'constraints.json',
|
|
41
|
+
claim: `constraint ${key} exists in registry`,
|
|
42
|
+
actual: 'no doc mentions it',
|
|
43
|
+
resolution: `add the constraint to the doc named by its source (${registry[key].source})`,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
for (const [budget, value] of Object.entries(config.budgets)) {
|
|
48
|
+
if (!Object.keys(registry).some((k) => k.endsWith(budget))) {
|
|
49
|
+
resolvable.push({
|
|
50
|
+
kind: 'dual-write',
|
|
51
|
+
doc: '.copperhead/config.json',
|
|
52
|
+
claim: `budget ${budget}=${value} configured`,
|
|
53
|
+
actual: 'not in constraints.json',
|
|
54
|
+
resolution: 'record_constraint with the budget value and its source',
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// PINOUT.md vs generated pins.h
|
|
59
|
+
const pinsH = path.join(repoRoot, 'firmware', 'src', 'pins.h');
|
|
60
|
+
if (existsSync(pinsH) && existsSync(path.join(docsDir, 'PINOUT.md'))) {
|
|
61
|
+
const header = await readFile(pinsH, 'utf8');
|
|
62
|
+
const pinout = await readFile(path.join(docsDir, 'PINOUT.md'), 'utf8');
|
|
63
|
+
for (const m of header.matchAll(/#define\s+PIN_(\w+)\s+(\S+)/g)) {
|
|
64
|
+
if (!pinout.includes(m[1])) {
|
|
65
|
+
resolvable.push({
|
|
66
|
+
kind: 'pins-h',
|
|
67
|
+
doc: 'firmware/src/pins.h',
|
|
68
|
+
claim: `defines PIN_${m[1]}`,
|
|
69
|
+
actual: 'PINOUT.md does not mention it',
|
|
70
|
+
resolution: 'regenerate pins.h from PINOUT.md (PINOUT.md is the single source of truth)',
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// coverage: docs the transparency layer expects
|
|
76
|
+
for (const name of ['DECISIONS.md', 'CHANGELOG.md']) {
|
|
77
|
+
if (!existsSync(path.join(docsDir, name))) {
|
|
78
|
+
resolvable.push({
|
|
79
|
+
kind: 'coverage',
|
|
80
|
+
doc: `${config.docs}${name}`,
|
|
81
|
+
claim: 'exists (transparency layer)',
|
|
82
|
+
actual: 'missing',
|
|
83
|
+
resolution: 'scaffold it (copperhead init creates it)',
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (existsSync(path.join(repoRoot, 'openspec', 'config.yaml'))) {
|
|
88
|
+
const res = await openspecValidate(repoRoot);
|
|
89
|
+
if (!res.ok) {
|
|
90
|
+
resolvable.push({
|
|
91
|
+
kind: 'openspec',
|
|
92
|
+
doc: 'openspec/',
|
|
93
|
+
claim: 'specs and changes validate',
|
|
94
|
+
actual: res.output.split('\n')[0] ?? 'validation failed',
|
|
95
|
+
resolution: 'fix the reported spec/change issues',
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// requirement violations: never auto-resolved (AC-7.3)
|
|
100
|
+
if (config.schematic && existsSync(path.join(repoRoot, config.schematic))) {
|
|
101
|
+
const pins = await pinNets(path.join(repoRoot, config.schematic));
|
|
102
|
+
for (const v of checkForbiddenPins(registry, pins)) {
|
|
103
|
+
violations.push({
|
|
104
|
+
kind: 'requirement-violation',
|
|
105
|
+
description: v.description,
|
|
106
|
+
governedBy: v.source,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return { resolvable, violations };
|
|
111
|
+
}
|
|
112
|
+
export function formatSyncReport(report) {
|
|
113
|
+
const lines = [];
|
|
114
|
+
if (!report.resolvable.length && !report.violations.length) {
|
|
115
|
+
return 'sync: no inconsistencies';
|
|
116
|
+
}
|
|
117
|
+
if (report.resolvable.length) {
|
|
118
|
+
lines.push(`${report.resolvable.length} resolvable inconsistency(ies):`);
|
|
119
|
+
for (const i of report.resolvable) {
|
|
120
|
+
lines.push(`- [${i.kind}] ${i.doc}: claims "${i.claim}" but actual is "${i.actual}"`);
|
|
121
|
+
lines.push(` resolution: ${i.resolution}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (report.violations.length) {
|
|
125
|
+
lines.push(`${report.violations.length} REQUIREMENT VIOLATION(S) (not auto-resolved; human decision needed):`);
|
|
126
|
+
for (const v of report.violations) {
|
|
127
|
+
lines.push(`- ${v.description}`);
|
|
128
|
+
lines.push(` governed by: ${v.governedBy}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return lines.join('\n');
|
|
132
|
+
}
|
|
133
|
+
export async function syncResolve(repoRoot, report, model, log) {
|
|
134
|
+
const reportText = formatSyncReport(report);
|
|
135
|
+
const res = await runAgentLoop({
|
|
136
|
+
repoRoot,
|
|
137
|
+
model,
|
|
138
|
+
request: 'resolve design-state inconsistencies found by copperhead sync',
|
|
139
|
+
stagePrompt: `You are resolving drift found by the deterministic sync verifier. The inconsistency report is below. Truth precedence: the KiCad files are ground truth for as-built facts (fix the docs to match); openspec specs and SPEC.md budgets are ground truth for requirements. Do NOT touch anything listed as a requirement violation; those are for the human. Apply each proposed resolution, verify, and finish.\n\n${reportText}`,
|
|
140
|
+
log,
|
|
141
|
+
});
|
|
142
|
+
return { ok: res.outcome === 'success' };
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"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"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
export const CONFIG_DIR = '.copperhead';
|
|
5
|
+
export const DEFAULTS = {
|
|
6
|
+
docs: 'docs/',
|
|
7
|
+
model: null,
|
|
8
|
+
maxTurns: 40,
|
|
9
|
+
maxRepairCycles: 5,
|
|
10
|
+
budgets: {},
|
|
11
|
+
};
|
|
12
|
+
export function configPath(repoRoot) {
|
|
13
|
+
return path.join(repoRoot, CONFIG_DIR, 'config.json');
|
|
14
|
+
}
|
|
15
|
+
export async function loadConfig(repoRoot) {
|
|
16
|
+
const p = configPath(repoRoot);
|
|
17
|
+
if (!existsSync(p)) {
|
|
18
|
+
return { schematic: null, board: null, ...DEFAULTS };
|
|
19
|
+
}
|
|
20
|
+
const raw = JSON.parse(await readFile(p, 'utf8'));
|
|
21
|
+
return {
|
|
22
|
+
schematic: raw.schematic ?? null,
|
|
23
|
+
board: raw.board ?? null,
|
|
24
|
+
docs: raw.docs ?? DEFAULTS.docs,
|
|
25
|
+
model: raw.model ?? null,
|
|
26
|
+
maxTurns: raw.maxTurns ?? DEFAULTS.maxTurns,
|
|
27
|
+
maxRepairCycles: raw.maxRepairCycles ?? DEFAULTS.maxRepairCycles,
|
|
28
|
+
budgets: raw.budgets ?? {},
|
|
29
|
+
...(raw.generatedHashes ? { generatedHashes: raw.generatedHashes } : {}),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Model selection precedence: flag > COPPERHEAD_MODEL > config > available key.
|
|
34
|
+
*
|
|
35
|
+
* Accepted values (same set for `--model`, COPPERHEAD_MODEL, and `model` in
|
|
36
|
+
* .copperhead/config.json):
|
|
37
|
+
*
|
|
38
|
+
* - `claude` : the Anthropic provider on its default model.
|
|
39
|
+
* - `claude-*`: any Anthropic model id, passed through verbatim, e.g.
|
|
40
|
+
* `claude-opus-4-5`. Anything starting with `claude` routes here.
|
|
41
|
+
* - `gpt-5` : the OpenAI provider on its default model.
|
|
42
|
+
* - anything else: sent to the OpenAI provider verbatim as a model id, e.g.
|
|
43
|
+
* `gpt-5-mini` or `o3`.
|
|
44
|
+
*
|
|
45
|
+
* Routing is prefix-based, not a fixed list (see makeProvider in agent/loop.ts),
|
|
46
|
+
* so a model released after this build still works without a code change. The
|
|
47
|
+
* cost is that a typo like `claud-sonnet-5` silently routes to OpenAI and fails
|
|
48
|
+
* there. The chosen provider must have its key set: ANTHROPIC_API_KEY for
|
|
49
|
+
* `claude*`, OPENAI_API_KEY otherwise.
|
|
50
|
+
*/
|
|
51
|
+
export function resolveModel(flag, config, env = process.env) {
|
|
52
|
+
if (flag)
|
|
53
|
+
return flag;
|
|
54
|
+
if (env.COPPERHEAD_MODEL)
|
|
55
|
+
return env.COPPERHEAD_MODEL;
|
|
56
|
+
if (config.model)
|
|
57
|
+
return config.model;
|
|
58
|
+
if (env.OPENAI_API_KEY)
|
|
59
|
+
return 'gpt-5';
|
|
60
|
+
if (env.ANTHROPIC_API_KEY)
|
|
61
|
+
return 'claude';
|
|
62
|
+
throw new Error('no model configured: pass --model, set COPPERHEAD_MODEL, set model in .copperhead/config.json, or provide OPENAI_API_KEY/ANTHROPIC_API_KEY');
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,IAAI,MAAM,WAAW,CAAC;AAc7B,MAAM,CAAC,MAAM,UAAU,GAAG,aAAa,CAAC;AAExC,MAAM,CAAC,MAAM,QAAQ,GAAkD;IACrE,IAAI,EAAE,OAAO;IACb,KAAK,EAAE,IAAI;IACX,QAAQ,EAAE,EAAE;IACZ,eAAe,EAAE,CAAC;IAClB,OAAO,EAAE,EAAE;CACZ,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,QAAgB;IACzC,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,EAAE,aAAa,CAAC,CAAC;AACxD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,QAAgB;IAC/C,MAAM,CAAC,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC/B,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;QACnB,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,QAAQ,EAAE,CAAC;IACvD,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC,CAA8B,CAAC;IAC/E,OAAO;QACL,SAAS,EAAE,GAAG,CAAC,SAAS,IAAI,IAAI;QAChC,KAAK,EAAE,GAAG,CAAC,KAAK,IAAI,IAAI;QACxB,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,QAAQ,CAAC,IAAI;QAC/B,KAAK,EAAE,GAAG,CAAC,KAAK,IAAI,IAAI;QACxB,QAAQ,EAAE,GAAG,CAAC,QAAQ,IAAI,QAAQ,CAAC,QAAQ;QAC3C,eAAe,EAAE,GAAG,CAAC,eAAe,IAAI,QAAQ,CAAC,eAAe;QAChE,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,EAAE;QAC1B,GAAG,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,GAAG,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACzE,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,YAAY,CAAC,IAAwB,EAAE,MAAwB,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG;IAChG,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IACtB,IAAI,GAAG,CAAC,gBAAgB;QAAE,OAAO,GAAG,CAAC,gBAAgB,CAAC;IACtD,IAAI,MAAM,CAAC,KAAK;QAAE,OAAO,MAAM,CAAC,KAAK,CAAC;IACtC,IAAI,GAAG,CAAC,cAAc;QAAE,OAAO,OAAO,CAAC;IACvC,IAAI,GAAG,CAAC,iBAAiB;QAAE,OAAO,QAAQ,CAAC;IAC3C,MAAM,IAAI,KAAK,CACb,4IAA4I,CAC7I,CAAC;AACJ,CAAC"}
|