Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,13 @@ macOS note: the vendored binary is unsigned — if Gatekeeper blocks it:
lines, `iter_*.png`, `result.toml`, `pulse.jld2`, `FINISHED`). Validate files with
`packages/schema/launcher/amico-validate <file>`.
- **New sessions open on opencode's `plan` agent** (plan-first posture for all
users): `buildOpencodeConfigContent` injects `default_agent: "plan"`; the
ordered picker is `plan → build → autodev → autoresearch` (default first,
then the director modes). The pulse-designer agent shell is retired (#389);
the interview content lives in the compiled AGENTS.md score section, visible
to every agent.
users): `buildOpencodeConfigContent` injects `default_agent: "plan"` and
`agent_order: ["plan", "develop", "research"]` — the three-mode surface
(spec-20260907-011500 D1, #858: autodev → develop, autoresearch → research;
old ids read-resolve for one release cycle; stock `build` is the implied
auto — a valid explicit id, out of the named set). The pulse-designer agent
shell is retired (#389); the interview content lives in the compiled
AGENTS.md score section, visible to every agent.
- Never commit to `main`; branch + PR.

## Changing opencode (the vendored fork)
Expand Down
15 changes: 8 additions & 7 deletions CONTEXT.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Amicode

The VSCode extension + CLI autoresearch studio — researchers propose, run, verify, and record experiments through a structured loop. Quantum optimal control is the primary Domain Pack; the product is the loop, not the domain.
The VSCode extension + CLI research studio — researchers propose, run, verify, and record experiments through a structured loop. Quantum optimal control is the primary Domain Pack; the product is the loop, not the domain.

## Language

Expand Down Expand Up @@ -71,12 +71,13 @@ _Avoid_: cron, scheduler (as concept names), night shift
The role that leads any autonomous loop — one canonical protocol (ledger discipline, dispatch through gates, analyze, record) that every campaign runs under, whichever mode it is bound to. Research and development differ in their gate packs, never in their director.
_Avoid_: conductor (standing decision)

**Autoresearch**:
The research mode: hypothesis queue → deliberate spec → experiment → gates → analyzer — the shipped, name-frozen autonomous mode, binding the research gate pack over the director core.
**Research**:
The research mode (renamed from `autoresearch` — the three-mode surface, spec-20260907-011500; old ids read-resolve for one release cycle, never migrated in place): hypothesis queue → deliberate spec → experiment → gates → analyzer — the shipped, name-frozen autonomous mode, binding the research gate pack over the director core.
_Avoid_: autoresearch (the pre-rename id — an alias at read time, not a name)

**Autodev**:
The development mode: issue DAG → TDD slices → CI/review → landed delta — the second autonomous mode, binding the dev gate pack. The loop is issue → PR → merge; automating the walk never weakens the dev gate or the never-merge-non-green rule.
_Avoid_: autobuild ("build" already means CI to everyone)
**Develop**:
The development mode (renamed from `autodev` — the three-mode surface, spec-20260907-011500; old ids read-resolve for one release cycle, never migrated in place): issue DAG → TDD slices → CI/review → landed delta — the second autonomous mode, binding the dev gate pack. The loop is issue → PR → merge; automating the walk never weakens the dev gate or the never-merge-non-green rule.
_Avoid_: autobuild ("build" already means CI to everyone), autodev (the pre-rename id — an alias at read time, not a name)

**Campaign**:
One bounded run of either autonomous mode, with a ledger and a closing artifact — the umbrella word for what a director executes. Copilot sessions are not campaigns; campaign-internal state (receipts, dispatch logs, scratch) crosses a campaign boundary only by distilling into issues, vault cards, or the artifact banks. Within a Research Project, campaign ledgers live at `ledger/campaigns/campaign-<YYYYMMDD>-<slug>.md`; outside a project, they live in the personal vault's `sessions/` directory.
Expand All @@ -86,7 +87,7 @@ _Avoid_: session (a copilot session is never a campaign; a campaign ledger is ne
The typed set of gates + phase templates an autonomous mode binds — the entire mode-specific part of the loop, held as committed data rather than prose, so the same director core runs any pack.

**Mode**:
One of the three director postures — copilot (the zeroth: default, interactive, packless), autoresearch, autodev. A mode binds a gate pack iff it is autonomous; the copilot mode binds none.
One of the three director postures — copilot (the zeroth: default, interactive, packless), research, develop. A mode binds a gate pack iff it is autonomous; the copilot mode binds none.
_Avoid_: surface, rail (they render and switch modes; a mode is a posture, not a surface)

### Fleet & serving
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ Skills are not configuration — they are the capability surface. **26 skills**

| Surface | What it covers |
|---|---|
| Research loop | `autoresearch`, `analyze`, `hypothesis-review`, `dream-reflect`, `open-threads`, `paper-writer`, `create-research-project`, `migrate-research-project` |
| Research loop | `research`, `analyze`, `hypothesis-review`, `dream-reflect`, `open-threads`, `paper-writer`, `create-research-project`, `migrate-research-project` |
| Lab + catalog + vault | `amico-lab`, `amico-catalog`, `amico-vault`, `amico-strategy`, `amico-schema-check`, `amico-slack` |
| Engineering | `debugging`, `tdd`, `verification`, `brainstorming`, `deliberate`, `grill-me`, `grill-with-docs`, `improve-codebase-architecture`, `teach`, `report-a-bug` |
| Entitled surfaces | `piccolissimo`, `intonatissimo` — usage guidance for the `-issimo` performance tiers; ship in the vsix, stage only for entitled sessions |
Expand Down
12 changes: 6 additions & 6 deletions packages/amico-run/test/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ function writeModeRegistryFixture(
);
};
bundle(
"autodev",
"develop",
["implementer"],
[
'closing_artifact = "landed-delta record"',
Expand Down Expand Up @@ -233,13 +233,13 @@ function writeModeRegistryFixture(
"",
"[[handoffs]]",
'kind = "hypothesis_seed"',
'target = "autoresearch"',
'target = "research"',
"",
].join("\n"),
"issue",
);
bundle(
"autoresearch",
"research",
["hypothesizer", "experimenter", "analyzer"],
[
'closing_artifact = "experiment note + ledger delta"',
Expand Down Expand Up @@ -276,7 +276,7 @@ function writeModeRegistryFixture(
"",
"[[handoffs]]",
'kind = "issue_seed"',
'target = "autodev"',
'target = "develop"',
"",
].join("\n"),
"hypothesis",
Expand Down Expand Up @@ -331,7 +331,7 @@ export function buildDoctorWorld(opts: DoctorWorldOpts = {}): DoctorWorld {

// ── agent cards: source (amicode repo) + both deployments + receipt ──
const agentsSrc = join(repoAmicode, "packages", "extension", "agents");
const CARDS = ["analyzer.md", "autodev.md", "autoresearch.md", "experimenter.md", "hypothesizer.md", "implementer.md", "librarian.md"];
const CARDS = ["analyzer.md", "develop.md", "research.md", "experimenter.md", "hypothesizer.md", "implementer.md", "librarian.md"];
for (const c of CARDS) {
mkdirSync(agentsSrc, { recursive: true });
writeFileSync(join(agentsSrc, c), `---\nmode: ${c.replace(".md", "")}\n---\n# ${c}\n`);
Expand Down Expand Up @@ -518,7 +518,7 @@ export function advanceRegistryOnRemote(bare: string, newTag: string, newRevisio
join(clone, "packages", "extension", "package.json"),
JSON.stringify({ name: "amicode", version: base }, null, 2) + "\n",
);
const pack = join(clone, "packages", "extension", "modes", "autodev", "pack.toml");
const pack = join(clone, "packages", "extension", "modes", "develop", "pack.toml");
writeFileSync(pack, readFileSync(pack, "utf8").replace("never delete tests to force green.", "never delete tests to force green. Bumped registry content."));
const index = join(clone, "packages", "extension", "modes", "release-index.toml");
writeFileSync(
Expand Down
46 changes: 23 additions & 23 deletions packages/amico-run/test/surfaces.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,11 @@ describe("doctor v2 surface inventory — stale cells", () => {

test("agent-cards-global stale: deployed card tampered (per-card digest diff)", async () => {
const w = buildDoctorWorld();
writeFileSync(join(w.config, "agents", "autodev.md"), "---\nmode: autodev\n---\n# TAMPERED\n");
writeFileSync(join(w.config, "agents", "develop.md"), "---\nmode: develop\n---\n# TAMPERED\n");
const report = await surfaceInventory(ctxForWorld(w));
const g = bySurface(report, "agent-cards-global");
expect(g.verdict).toBe("stale");
expect(g.evidence.join(" ")).toMatch(/card autodev\.md changed/);
expect(g.evidence.join(" ")).toMatch(/card develop\.md changed/);
const st = bySurface(report, "agent-cards-staging");
expect(st.verdict).toBe("current"); // the OTHER deployment is unaffected
cleanup();
Expand All @@ -171,12 +171,12 @@ describe("doctor v2 surface inventory — stale cells", () => {
const w = buildDoctorWorld();
const receiptPath = join(w.repoAmicode, "packages", "extension", "agents", ".deploy-receipt.json");
const receipt = JSON.parse(readFileSync(receiptPath, "utf8")) as { sources: { card: string; sha256: string }[] };
receipt.sources.find((s) => s.card === "autodev.md")!.sha256 = "sha256:" + "0".repeat(64); // lies about autodev.md, by name — order-independent (the fixture stages the full 7-card surface)
receipt.sources.find((s) => s.card === "develop.md")!.sha256 = "sha256:" + "0".repeat(64); // lies about develop.md, by name — order-independent (the fixture stages the full 7-card surface)
writeFileSync(receiptPath, JSON.stringify(receipt, null, 2) + "\n");
const report = await surfaceInventory(ctxForWorld(w));
const g = bySurface(report, "agent-cards-global");
expect(g.verdict).toBe("stale");
expect(g.evidence.join(" ")).toMatch(/receipt source digest for autodev\.md ≠ current source/);
expect(g.evidence.join(" ")).toMatch(/receipt source digest for develop\.md ≠ current source/);
cleanup();
});
});
Expand Down Expand Up @@ -480,11 +480,11 @@ describe("doctor mode-registry component verdicts (#804)", () => {
}
// every bundle component of the fixture registry is walked
const components = r.components!.map((c) => `${c.mode}/${c.component}`);
expect(components).toContain("autodev/card.md");
expect(components).toContain("autodev/pack.toml");
expect(components).toContain("autodev/mode.toml");
expect(components).toContain("autodev/roles/implementer.md");
expect(components).toContain("autoresearch/roles/hypothesizer.md");
expect(components).toContain("develop/card.md");
expect(components).toContain("develop/pack.toml");
expect(components).toContain("develop/mode.toml");
expect(components).toContain("develop/roles/implementer.md");
expect(components).toContain("research/roles/hypothesizer.md");
expect(components).toContain("registry/release-compare");
expect(components).toContain("registry/deploy-receipt");
}
Expand All @@ -493,14 +493,14 @@ describe("doctor mode-registry component verdicts (#804)", () => {

test("tampering ONE bundle component flips the record stale with the component NAMED (AC4)", async () => {
const w = buildDoctorWorld();
writeFileSync(join(w.config, "modes", "autoresearch", "pack.toml"), "# TAMPERED PACK\n");
writeFileSync(join(w.config, "modes", "research", "pack.toml"), "# TAMPERED PACK\n");
const report = await surfaceInventory(ctxForWorld(w));
const g = bySurface(report, "agent-cards-global");
expect(g.verdict).toBe("stale");
const named = componentOf(g, (c) => c.mode === "autoresearch" && c.component === "pack.toml");
const named = componentOf(g, (c) => c.mode === "research" && c.component === "pack.toml");
expect(named).toBeDefined();
expect(named!.verdict).toBe("stale");
expect(named!.evidence.join(" ")).toMatch(/component autoresearch\/pack\.toml changed/);
expect(named!.evidence.join(" ")).toMatch(/component research\/pack\.toml changed/);
// the OTHER deployment is unaffected
expect(bySurface(report, "agent-cards-staging").verdict).toBe("current");
cleanup();
Expand All @@ -511,11 +511,11 @@ describe("doctor mode-registry component verdicts (#804)", () => {
// delete the deployed role file: the manifest DECLARES it, the deployed
// set lacks it — the same violation the vitest-side validator cell
// pins on the SOURCE tree, judged here on the DEPLOYED tree
rmSync(join(w.config, "modes", "autodev", "roles", "implementer.md"), { force: true });
rmSync(join(w.config, "modes", "develop", "roles", "implementer.md"), { force: true });
const report = await surfaceInventory(ctxForWorld(w));
const g = bySurface(report, "agent-cards-global");
expect(g.verdict).toBe("stale");
const named = componentOf(g, (c) => c.mode === "autodev" && c.component === "roles/implementer.md");
const named = componentOf(g, (c) => c.mode === "develop" && c.component === "roles/implementer.md");
expect(named).toBeDefined();
expect(named!.verdict).toBe("stale");
expect(named!.evidence.join(" ")).toMatch(/missing from the deployed set/);
Expand All @@ -528,13 +528,13 @@ describe("doctor mode-registry component verdicts (#804)", () => {
test("a half-staged bundle (card new, roles old) reads stale with roles named — never current (AC4)", async () => {
const w = buildDoctorWorld();
// the card keeps matching the release; the role copy is OLD (stale bytes)
writeFileSync(join(w.config, "modes", "autodev", "roles", "implementer.md"), "---\nmode: implementer\n---\n# OLD ROLE BYTES\n");
writeFileSync(join(w.config, "modes", "develop", "roles", "implementer.md"), "---\nmode: implementer\n---\n# OLD ROLE BYTES\n");
const report = await surfaceInventory(ctxForWorld(w));
const g = bySurface(report, "agent-cards-global");
expect(g.verdict).toBe("stale");
const card = componentOf(g, (c) => c.mode === "autodev" && c.component === "card.md");
const card = componentOf(g, (c) => c.mode === "develop" && c.component === "card.md");
expect(card!.verdict).toBe("current"); // the card alone is fine —
const role = componentOf(g, (c) => c.mode === "autodev" && c.component === "roles/implementer.md");
const role = componentOf(g, (c) => c.mode === "develop" && c.component === "roles/implementer.md");
expect(role!.verdict).toBe("stale"); // — but the bundle as a unit is stale
expect(role!.evidence.join(" ")).toMatch(/roles\/implementer\.md changed/);
cleanup();
Expand Down Expand Up @@ -590,12 +590,12 @@ describe("doctor mode-registry component verdicts (#804)", () => {

test("a version gap between the bundle's doctor floor and THIS doctor fails LOUDLY (AC5)", async () => {
const w = buildDoctorWorld();
const manifestPath = join(w.config, "modes", "autodev", "mode.toml");
const manifestPath = join(w.config, "modes", "develop", "mode.toml");
writeFileSync(manifestPath, readFileSync(manifestPath, "utf8").replace('doctor = "1"', 'doctor = "2"'));
const report = await surfaceInventory(ctxForWorld(w));
const g = bySurface(report, "agent-cards-global");
expect(g.verdict).toBe("stale");
const floorRow = componentOf(g, (c) => c.mode === "autodev" && c.component === "version-floor");
const floorRow = componentOf(g, (c) => c.mode === "develop" && c.component === "version-floor");
expect(floorRow!.verdict).toBe("failed");
expect(floorRow!.evidence.join(" ")).toMatch(/version gap/);
expect(floorRow!.evidence.join(" ")).toMatch(/never a silent degrade/);
Expand All @@ -619,7 +619,7 @@ describe("doctor mode-registry component verdicts (#804)", () => {
// the deployed bytes still match the machine's OWN release — the bundle
// components themselves are current; the release compare is the staleness
const g = bySurface(report, "agent-cards-global");
expect(componentOf(g, (c) => c.mode === "autodev" && c.component === "card.md")!.verdict).toBe("current");
expect(componentOf(g, (c) => c.mode === "develop" && c.component === "card.md")!.verdict).toBe("current");
cleanup();
});

Expand Down Expand Up @@ -671,13 +671,13 @@ describe("doctor mode-registry component verdicts (#804)", () => {
const w = buildDoctorWorld();
// drift the CHECKOUT's registry ahead (uncommitted) — deployed matches tag
writeFileSync(
join(w.repoAmicode, "packages", "extension", "modes", "autodev", "pack.toml"),
join(w.repoAmicode, "packages", "extension", "modes", "develop", "pack.toml"),
"# CHECKOUT-ONLY DRIFT\n",
);
const report = await surfaceInventory(ctxForWorld(w));
const g = bySurface(report, "agent-cards-global");
expect(g.verdict).toBe("current");
expect(componentOf(g, (c) => c.mode === "autodev" && c.component === "pack.toml")!.verdict).toBe("current");
expect(componentOf(g, (c) => c.mode === "develop" && c.component === "pack.toml")!.verdict).toBe("current");
cleanup();
});
});
Expand Down Expand Up @@ -794,7 +794,7 @@ describe("doctor v2 JSON contract", () => {
{
surface: "agent-cards-global", version: "1", source_version: "1", verdict: "current", evidence: ["ok"],
components: [
{ mode: "autodev", component: "card.md", verdict: "current", evidence: ["byte-matches release"] },
{ mode: "develop", component: "card.md", verdict: "current", evidence: ["byte-matches release"] },
{ mode: "registry", component: "release-compare", verdict: "stale", evidence: ["current to v0.3.1, stale to release v0.3.2"] },
],
},
Expand Down
Loading
Loading