@uniqbit/mate-core 0.15.4-canary.8 → 0.15.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/claude-plugin/hooks/artifact-finish-nudge.mjs +5 -3
  2. package/claude-plugin/hooks/session-banner.mjs +5 -3
  3. package/claude-plugin/hooks/ts-loader.mjs +28 -0
  4. package/claude-plugin/hooks/validate-artifact-path.mjs +5 -3
  5. package/package.json +1 -1
  6. package/src/cli/commands/cap/openspec.ts +20 -1
  7. package/src/cli/commands/companion/hub.ts +1 -0
  8. package/src/cli/commands/plugin/install.ts +15 -3
  9. package/src/hooks/artifact-finish-nudge.ts +35 -3
  10. package/src/lib/context-mode-package.ts +5 -3
  11. package/src/lib/orchestrator/adapters/opencode.ts +10 -60
  12. package/src/lib/orchestrator/companion-git-sync.ts +67 -8
  13. package/src/lib/orchestrator/companion-hub.ts +28 -6
  14. package/src/lib/orchestrator/types.ts +2 -2
  15. package/src/lib/package-paths.ts +1 -0
  16. package/src/opencode/companion-hooks.ts +33 -12
  17. package/src/playbooks/companion-guidance.ts +1 -1
  18. package/src/templates/capabilities/openspec-cap/mate-skills/agents/mate-openspec-backfill/SKILL.md +65 -0
  19. package/src/templates/capabilities/openspec-cap/mate-v1/schema.yaml +1 -0
  20. package/src/templates/root/TEMPLATE_AGENTS.md +2 -0
  21. package/src/templates/root/TEMPLATE_CLAUDE.md +2 -0
  22. package/src/tools/setup/__snapshots__/runtime-surface-golden.test.ts.snap +3612 -0
  23. package/src/tools/setup/capabilities/context-mode.ts +57 -83
  24. package/src/tools/setup/capabilities/graphify-shared.ts +27 -0
  25. package/src/tools/setup/capabilities/graphify.ts +86 -295
  26. package/src/tools/setup/capabilities/openspec.ts +34 -1
  27. package/src/tools/setup/capabilities/react-doctor.ts +37 -37
  28. package/src/tools/setup/capabilities/rtk.ts +4 -1
  29. package/src/tools/setup/capabilities/tokensave-shared.ts +6 -0
  30. package/src/tools/setup/capabilities/tokensave.ts +51 -72
  31. package/src/tools/setup/context-services.ts +29 -0
  32. package/src/tools/setup/dynamic-plugins/hydrate.ts +15 -1
  33. package/src/tools/setup/engine.ts +68 -1
  34. package/src/tools/setup/mate.ts +10 -2
  35. package/src/tools/setup/plugin.ts +105 -0
  36. package/src/tools/setup/plugins/gitignore.ts +7 -4
  37. package/src/tools/setup/plugins/guidance.ts +1 -1
  38. package/src/tools/setup/providers/agent-file-sections.ts +78 -0
  39. package/src/tools/setup/providers/claude-format.ts +159 -0
  40. package/src/tools/setup/providers/claude.ts +281 -227
  41. package/src/tools/setup/providers/opencode-format.ts +146 -0
  42. package/src/tools/setup/providers/opencode.ts +209 -60
  43. package/src/tools/setup/providers/skill-tree.ts +38 -0
  44. package/src/tools/setup.ts +7 -56
@@ -179,15 +179,12 @@ function detectCommandArchive(input: unknown, output: unknown): string | null {
179
179
 
180
180
  function detectNewlyArchivedChanges(archiveDir: string, snapshot: Set<string>): string[] {
181
181
  const current = readArchiveEntries(archiveDir);
182
- const newlyArchived = [...current].filter((entry) => !snapshot.has(entry)).toSorted();
183
- snapshot.clear();
184
- for (const entry of current) snapshot.add(entry);
185
- return newlyArchived;
182
+ return [...current].filter((entry) => !snapshot.has(entry)).toSorted();
186
183
  }
187
184
 
188
185
  function appendOpenSpecFinishNudge(
189
186
  context: CompanionContext,
190
- archiveSnapshot: Set<string>,
187
+ archiveSnapshot: Set<string> | undefined,
191
188
  nudgedCommandChanges: Set<string>,
192
189
  input: { tool?: unknown },
193
190
  output: { output?: string },
@@ -196,8 +193,10 @@ function appendOpenSpecFinishNudge(
196
193
 
197
194
  const archiveDir = path.join(context.companionPath, "openspec", "changes", "archive");
198
195
  const changes: string[] = [];
199
- for (const entry of detectNewlyArchivedChanges(archiveDir, archiveSnapshot)) {
200
- changes.push(entry.slice("YYYY-MM-DD-".length));
196
+ if (archiveSnapshot) {
197
+ for (const entry of detectNewlyArchivedChanges(archiveDir, archiveSnapshot)) {
198
+ changes.push(entry.slice("YYYY-MM-DD-".length));
199
+ }
201
200
  }
202
201
  const commandChange =
203
202
  String(input.tool ?? "").toLowerCase() === "bash" ? detectCommandArchive(input, output) : null;
@@ -313,22 +312,36 @@ async function runReactDoctorScan(
313
312
  }
314
313
 
315
314
  type PluginEventInput = Parameters<NonNullable<Hooks["event"]>>[0];
315
+ type ToolBeforeInput = Parameters<NonNullable<Hooks["tool.execute.before"]>>[0];
316
+ type ToolBeforeOutput = Parameters<NonNullable<Hooks["tool.execute.before"]>>[1];
316
317
  type ToolAfterInput = Parameters<NonNullable<Hooks["tool.execute.after"]>>[0];
317
318
  type ToolAfterOutput = Parameters<NonNullable<Hooks["tool.execute.after"]>>[1];
318
319
 
320
+ interface ArchiveCallSnapshot {
321
+ sessionID: string;
322
+ entries: Set<string>;
323
+ }
324
+
319
325
  export const CompanionHooksPlugin: Plugin = async (pluginInput = {} as PluginInput) => {
320
326
  const { client, $ } = pluginInput;
321
327
  const context = readContext(process.env.MATE_ARTIFACT_PATH ?? "");
322
328
  if (!context.companionPath || !context.repositoryPath) return {};
323
329
 
324
330
  const archiveDir = path.join(context.companionPath, "openspec", "changes", "archive");
325
- const archiveSnapshot = readArchiveEntries(archiveDir);
331
+ const archiveCallSnapshots = new Map<string, ArchiveCallSnapshot>();
326
332
  const nudgedCommandChanges = new Set<string>();
327
333
  const dirtyReactDoctorSessions = new Set<string>();
328
334
  const reactDoctorScansInFlight = new Set<string>();
329
335
 
330
336
  return {
331
337
  event: async ({ event }: PluginEventInput) => {
338
+ if (event.type === "session.deleted") {
339
+ const sessionID = event.properties.info.id;
340
+ for (const [callID, snapshot] of archiveCallSnapshots) {
341
+ if (snapshot.sessionID === sessionID) archiveCallSnapshots.delete(callID);
342
+ }
343
+ return;
344
+ }
332
345
  if (event.type !== "session.idle") return;
333
346
  const sessionID = event.properties.sessionID;
334
347
  if (
@@ -340,10 +353,7 @@ export const CompanionHooksPlugin: Plugin = async (pluginInput = {} as PluginInp
340
353
  }
341
354
  await runReactDoctorScan(context, client, $, sessionID, reactDoctorScansInFlight);
342
355
  },
343
- "tool.execute.before": async (
344
- input: { tool: unknown },
345
- output: { args: { filePath?: unknown; patchText?: unknown } | undefined },
346
- ) => {
356
+ "tool.execute.before": async (input: ToolBeforeInput, output: ToolBeforeOutput) => {
347
357
  const toolName = String(input.tool ?? "");
348
358
  const args = output.args ?? {};
349
359
  if (["write", "edit"].includes(toolName)) {
@@ -359,13 +369,24 @@ export const CompanionHooksPlugin: Plugin = async (pluginInput = {} as PluginInp
359
369
  }
360
370
  }
361
371
  }
372
+ if (context.gitAutoModeEnabled) {
373
+ archiveCallSnapshots.set(input.callID, {
374
+ sessionID: input.sessionID,
375
+ entries: readArchiveEntries(archiveDir),
376
+ });
377
+ }
362
378
  },
363
379
  "tool.execute.after": async (input: ToolAfterInput, output: ToolAfterOutput) => {
364
380
  if (context.reactDoctorEnabled && REACT_DOCTOR_EDIT_TOOLS.has(input.tool)) {
365
381
  dirtyReactDoctorSessions.add(input.sessionID);
366
382
  }
383
+ const archiveSnapshot = archiveCallSnapshots.get(input.callID)?.entries;
384
+ archiveCallSnapshots.delete(input.callID);
367
385
  appendOpenSpecFinishNudge(context, archiveSnapshot, nudgedCommandChanges, input, output);
368
386
  },
387
+ dispose: async () => {
388
+ archiveCallSnapshots.clear();
389
+ },
369
390
  };
370
391
  };
371
392
 
@@ -103,7 +103,7 @@ export function buildCompanionPolicyXml(
103
103
 
104
104
  if (hasOpenspecCapability(context.capabilities)) {
105
105
  lines.push(
106
- ` <rule id="openspec-finish" severity="critical">Finish OpenSpec changes ONLY with: ${FRAMEWORK_NAME} artifact finish "<name>" --json — never hand-commit or hand-tag a finish. Finishing a still-active change applies its delta specs itself, so do not pre-apply them to openspec/specs right before finishing. Finishing an already-archived change resumes without re-applying delta specs, so an archive flow that already synced specs (e.g. openspec-sync-specs) composes fine with a finish afterwards.</rule>`,
106
+ ` <rule id="openspec-finish" severity="critical">Finish OpenSpec changes by archiving them: the archive triggers a nudge directing you to run ${FRAMEWORK_NAME} artifact finish "<name>" --json — if no nudge arrives, invoke that command yourself. It is the only sanctioned completion; never hand-commit or hand-tag a finish. Finishing a still-active change archives it and applies its delta specs itself, so do not pre-apply them to openspec/specs right before finishing. Finishing an already-archived change resumes without re-applying delta specs, so an archive flow that already synced specs (e.g. openspec-sync-specs) composes fine with a finish afterwards.</rule>`,
107
107
  );
108
108
  }
109
109
 
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: mate-openspec-backfill
3
+ description: Reverse-engineer an OpenSpec spec for one existing feature and emit a ready-to-finish backfill change. Use when the user wants to backfill, document, or spec existing or legacy behavior that has no spec yet.
4
+ allowed-tools: Bash(openspec:*), Bash(mate:*)
5
+ license: MIT
6
+ compatibility: Requires the mate CLI and the openspec capability enabled.
7
+ metadata:
8
+ author: mate
9
+ version: "1.0"
10
+ ---
11
+
12
+ Create a spec for one feature that already exists in the working repository. The run ends with a standard ready-to-finish change — it never edits main specs and never finishes.
13
+
14
+ ## Scope rules
15
+
16
+ - **One named feature per run.** Refuse Area-wide or repository-wide sweeps; ask the user to name a single feature and run the skill once per feature.
17
+ - **Interactive by design.** Every ambiguity and every suspected bug becomes a user question. Do not run this skill unattended.
18
+
19
+ ## Steps
20
+
21
+ 1. **Scope.** Map the named feature to code: entry points, callees, tests. Use whatever exploration tooling this project has enabled (code-graph or index tools when present, otherwise search and targeted reading) — assume no specific capability is installed. Then check `openspec/specs/` for an existing capability covering this domain — prefer extending it (`MODIFIED`/`ADDED` deltas) over minting a new capability id.
22
+
23
+ 2. **Sweep.** Extract candidate behaviors and tag each finding:
24
+ - `[test-backed]` — an existing test verifies it (strongest; scenarios translate almost directly from tests)
25
+ - `[code-only]` — observable in code but untested
26
+ - `[inferred]` — assumed intent without direct evidence
27
+
28
+ Every candidate requirement needs at least one citation: a test name or `file:line`. Docs and comments corroborate but never stand alone. `[inferred]` findings are not requirements — they become questions for step 3.
29
+
30
+ 3. **Ask.** Batch the open questions to the user:
31
+ - Behavior that looks unintended → the user rules **spec the actual behavior** or **spec the intent** (with a follow-up fix change). Suspected bugs never silently become requirements.
32
+ - `[inferred]` findings → confirm, demote to out-of-scope, or convert to a question the emitted proposal records as open.
33
+
34
+ 4. **Emit.** Create the change and build its artifacts in dependency order:
35
+
36
+ ```bash
37
+ openspec new change "backfill-spec-<capability>"
38
+ openspec status --change "backfill-spec-<capability>" --json
39
+ openspec instructions <artifact-id> --change "backfill-spec-<capability>" --json
40
+ ```
41
+
42
+ The artifact set comes from the active schema (`schemaName` in the status JSON) — never assume a fixed artifact list. Follow each artifact's returned instructions and template, and state the active schema in the proposal so reviewers know which workflow produced the change. Map the backfill roles onto whatever artifacts the schema defines:
43
+
44
+ | Backfill role | Typical artifact (mate-v1 example) |
45
+ | ---------------------------------------------------------------------------------------------- | ---------------------------------- |
46
+ | Scope decisions and rulings from step 3 | explore-brief.md |
47
+ | "Documents existing behavior, no code changes" + open questions | proposal.md |
48
+ | `ADDED`/`MODIFIED` requirements, behavior only, one citation each | specs/ |
49
+ | As-built evidence dossier: entry points, test inventory, `file:line` citations per requirement | design.md |
50
+ | Verification checklist: one task per requirement, "confirm behavior at <citation>" | tasks.md |
51
+
52
+ The verification task artifact MUST open with this rule, verbatim, so the applying agent sees it without knowing this skill: "These are verification tasks for a docs-only backfill change. If a requirement fails verification, update the delta spec (reword, drop, or re-cite the requirement) — never modify code in this change. A real bug found here becomes a separate fix change."
53
+
54
+ Requirements state observable contracts, never implementation detail ("propagates the child exit code", not "uses spawnSync").
55
+
56
+ 5. **Stop.** Report the change as ready-to-finish and hand off:
57
+ - Verify: `openspec-apply-change` works through tasks.md, checking each requirement against the code.
58
+ - Finish: `mate-artifact-finish` applies the deltas to main specs and anchors the change.
59
+
60
+ ## Guardrails
61
+
62
+ - Never write files under `openspec/specs/` — main specs change only through finished changes.
63
+ - Never invoke any finish flow (`mate artifact finish`, `openspec archive`); stop at ready-to-finish.
64
+ - Never emit a requirement without a citation, and never spec a suspected bug without the user's ruling.
65
+ - Keep capability ids opaque kebab-case; extend existing capabilities before creating new ones.
@@ -273,4 +273,5 @@ apply:
273
273
  instruction: |
274
274
  Read context files and work through pending tasks only within the proposal's affected Areas. Mark complete as you go.
275
275
  When reporting information to me, be extremely concise and sacrifice grammar for the sake of concision. Apply this same preference to JSDoc.
276
+ Code comments: JSDoc format only (/** ... */), never //. Sparse — only non-obvious invariants or constraints, never restated artifact rationale.
276
277
  Pause if you hit blockers or need clarification.
@@ -9,7 +9,9 @@ This block is kept for AGENTS.md compatibility and must not restate that policy.
9
9
 
10
10
  - Use absolute paths when a tool needs a file path. Do not create literal `$MATE_REPO_PATH` or `$MATE_ARTIFACT_PATH` directories.
11
11
  - When reporting information to me, be extremely concise and sacrifice grammar for the sake of concision. Apply this same preference to JSDoc.
12
+ - Code comments: JSDoc format only (`/** ... */`), never `//`. Sparse — only non-obvious invariants or constraints, never restated artifact rationale.
12
13
  - Never add a `Co-Authored-By: <model>` trailer or model-attribution footer to commit messages.
13
14
  - Never commit, push, or open a pull request in the working repo (`$MATE_REPO_PATH`) unless the user explicitly asks for it.
15
+ - Never connect to a database (local or remote), touch live/external systems (deploys, infra), or take destructive/irreversible actions without asking the user first.
14
16
 
15
17
  <!-- MATE:COMPANION:END -->
@@ -9,9 +9,11 @@ This block is kept for Claude/AGENTS.md compatibility and must not restate that
9
9
 
10
10
  - Use absolute paths when a tool needs a file path. Do not create literal `$MATE_REPO_PATH` or `$MATE_ARTIFACT_PATH` directories.
11
11
  - When reporting information to me, be extremely concise and sacrifice grammar for the sake of concision. Apply this same preference to JSDoc.
12
+ - Code comments: JSDoc format only (`/** ... */`), never `//`. Sparse — only non-obvious invariants or constraints, never restated artifact rationale.
12
13
  - Claude Code receives `$MATE_ARTIFACT_PATH` via `--add-dir`; if `@` autocomplete does not show companion artifacts, reference them by absolute path.
13
14
  - The root `CLAUDE.md` in the companion repo is intentional and loaded by Mate. Do not create another project-level `CLAUDE.md` in the working repo unless the user explicitly asks for one.
14
15
  - Never add a `Co-Authored-By: <model>` trailer or model-attribution footer to commit messages.
15
16
  - Never commit, push, or open a pull request in the working repo (`$MATE_REPO_PATH`) unless the user explicitly asks for it.
17
+ - Never connect to a database (local or remote), touch live/external systems (deploys, infra), or take destructive/irreversible actions without asking the user first.
16
18
 
17
19
  <!-- MATE:COMPANION:END -->