@uniqbit/mate-core 0.18.4 → 1.0.0-canary.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.
Files changed (231) hide show
  1. package/claude-plugin/hooks/companion-instructions.mjs +9 -0
  2. package/claude-plugin/hooks/hooks.json +12 -2
  3. package/opencode-plugin/src/add-dir.ts +76 -10
  4. package/opencode-plugin/src/companion.ts +1 -8
  5. package/opencode-plugin/src/server.ts +2 -15
  6. package/package.json +10 -2
  7. package/src/brand/art.generated.ts +69 -0
  8. package/src/brand/index.ts +12 -0
  9. package/src/brand/palette.ts +19 -0
  10. package/src/brand/svg.generated.ts +8 -0
  11. package/src/brand/terminal.ts +83 -0
  12. package/src/brand/types.ts +33 -0
  13. package/src/cli/commands/artifact/artifact.ts +8 -0
  14. package/src/cli/commands/artifact/delivery/archive.ts +117 -0
  15. package/src/cli/commands/artifact/delivery/branch-flags.ts +55 -0
  16. package/src/cli/commands/artifact/delivery/environment.ts +104 -0
  17. package/src/cli/commands/artifact/delivery/link.ts +154 -0
  18. package/src/cli/commands/artifact/delivery/output.ts +58 -0
  19. package/src/cli/commands/artifact/finish/command.ts +33 -3
  20. package/src/cli/commands/artifact/finish/engine.ts +47 -3
  21. package/src/cli/commands/artifact/finish/finisher.ts +10 -0
  22. package/src/cli/commands/artifact/finish/openspec.ts +200 -129
  23. package/src/cli/commands/artifact/finish/secret-scan.ts +43 -0
  24. package/src/cli/commands/artifact/pending/command.ts +31 -3
  25. package/src/cli/commands/artifact/pending/discovery.ts +21 -57
  26. package/src/cli/commands/cap/graphify.ts +52 -3
  27. package/src/cli/commands/cap/index-cmd.ts +8 -1
  28. package/src/cli/commands/cap/openspec.ts +13 -25
  29. package/src/cli/commands/companion/open.ts +56 -39
  30. package/src/cli/commands/companion/update.ts +75 -42
  31. package/src/cli/commands/doctor.ts +257 -78
  32. package/src/cli/commands/install.ts +0 -8
  33. package/src/cli/commands/plugin/install.ts +9 -4
  34. package/src/cli/commands/plugin/plugin.ts +3 -2
  35. package/src/cli/commands/report/adapter.ts +11 -2
  36. package/src/cli/commands/report/contract.ts +21 -0
  37. package/src/cli/commands/report/highlight.ts +34 -4
  38. package/src/cli/commands/report/markdown.ts +110 -0
  39. package/src/cli/commands/report/palette.ts +118 -0
  40. package/src/cli/commands/report/renderer.ts +456 -153
  41. package/src/cli/commands/report/types.ts +16 -0
  42. package/src/cli/commands/setup.ts +7 -15
  43. package/src/cli/commands/studio/client-asset.ts +16 -0
  44. package/src/cli/commands/studio/openspec-cli.ts +10 -58
  45. package/src/cli/commands/studio/routes.ts +2 -0
  46. package/src/cli/commands/studio/selection.ts +19 -1
  47. package/src/cli/commands/studio/server.ts +42 -1
  48. package/src/cli/commands/studio/terminal-launch.ts +7 -16
  49. package/src/cli/commands/studio/views/client.ts +6 -616
  50. package/src/cli/commands/studio/views/dashboard/changes.tsx +5 -10
  51. package/src/cli/commands/studio/views/document.tsx +137 -69
  52. package/src/cli/commands/studio/views/markdown.tsx +250 -0
  53. package/src/cli/commands/studio/views/palette.ts +134 -0
  54. package/src/cli/commands/studio/views/reports/index.tsx +2 -7
  55. package/src/cli/commands/studio/views/skills/index.tsx +43 -62
  56. package/src/cli/commands/studio/views/specs/index.tsx +7 -15
  57. package/src/cli/commands/studio/views/studio-client/actions.ts +51 -0
  58. package/src/cli/commands/studio/views/studio-client/companion.ts +13 -0
  59. package/src/cli/commands/studio/views/studio-client/index.ts +32 -0
  60. package/src/cli/commands/studio/views/studio-client/keys.ts +3 -0
  61. package/src/cli/commands/studio/views/studio-client/main.ts +3 -0
  62. package/src/cli/commands/studio/views/studio-client/page.ts +146 -0
  63. package/src/cli/commands/studio/views/studio-client/panels.ts +57 -0
  64. package/src/cli/commands/studio/views/studio-client/reports.ts +122 -0
  65. package/src/cli/commands/studio/views/studio-client/theme.ts +63 -0
  66. package/src/cli/commands/studio/views/studio-client/vault-editor.ts +166 -0
  67. package/src/cli/commands/studio/views/studio-client/vault-pane.ts +202 -0
  68. package/src/cli/commands/studio/views/studio-client/vault-tree.ts +258 -0
  69. package/src/cli/commands/studio/views/studio-client.generated.ts +2 -0
  70. package/src/cli/commands/studio/views/styles.ts +217 -206
  71. package/src/cli/commands/studio/views/vault/state.ts +3 -5
  72. package/src/cli/commands/studio/views/workflow/delivery-path.tsx +370 -0
  73. package/src/cli/commands/studio/views/workflow/graph.ts +220 -0
  74. package/src/cli/commands/studio/views/workflow/index.tsx +2 -3
  75. package/src/cli/commands/studio/views/workflow/steps.ts +228 -287
  76. package/src/cli/commands/wrap.ts +9 -42
  77. package/src/cli/main.ts +4 -1
  78. package/src/cli/parse-flags.ts +25 -7
  79. package/src/cli/setup-selector.tsx +38 -162
  80. package/src/cli/usage.ts +5 -2
  81. package/src/hooks/companion-instructions.ts +190 -0
  82. package/src/hooks/hook-io.ts +63 -0
  83. package/src/hooks/session-banner.ts +21 -29
  84. package/src/hooks/session-guidance.ts +10 -36
  85. package/src/hooks/session-prepare.ts +115 -28
  86. package/src/hooks/validate-artifact-path.ts +82 -24
  87. package/src/index.ts +4 -0
  88. package/src/lib/change-layout.ts +154 -0
  89. package/src/lib/components/wizard-header.tsx +3 -1
  90. package/src/lib/context-mode-package.ts +24 -11
  91. package/src/lib/delivery/archive-change.ts +154 -0
  92. package/src/lib/delivery/branches.ts +108 -0
  93. package/src/lib/delivery/environment.ts +29 -0
  94. package/src/lib/delivery/forge.ts +279 -0
  95. package/src/lib/delivery/overlap.ts +202 -0
  96. package/src/lib/delivery/record-delivery.ts +196 -0
  97. package/src/lib/delivery/record.ts +343 -0
  98. package/src/lib/delivery/refresh.ts +174 -0
  99. package/src/lib/delivery/repository-access.ts +97 -0
  100. package/src/lib/delivery/stages.ts +111 -0
  101. package/src/lib/delivery/token.ts +58 -0
  102. package/src/lib/frontmatter.ts +18 -0
  103. package/src/lib/opencode-plugin-package.ts +5 -5
  104. package/src/lib/openspec-cli.ts +116 -0
  105. package/src/lib/openspec-scopes.ts +86 -0
  106. package/src/lib/orchestrator/adapters/claude-args.ts +50 -0
  107. package/src/lib/orchestrator/adapters/claude.ts +60 -162
  108. package/src/lib/orchestrator/adapters/opencode.ts +9 -24
  109. package/src/lib/orchestrator/config-store.ts +2 -70
  110. package/src/lib/orchestrator/editor.ts +9 -9
  111. package/src/lib/orchestrator/openspec-store.ts +14 -54
  112. package/src/lib/orchestrator/projection-claude-commands.ts +36 -0
  113. package/src/lib/orchestrator/projection-claude-links.ts +159 -0
  114. package/src/lib/orchestrator/projection-claude-skills.ts +19 -190
  115. package/src/lib/orchestrator/projection-entries.ts +45 -15
  116. package/src/lib/orchestrator/projection-runtime-documents.ts +15 -6
  117. package/src/lib/orchestrator/projection-types.ts +3 -2
  118. package/src/lib/orchestrator/refresh-working-claude-links.ts +87 -0
  119. package/src/lib/orchestrator/session-prepare.ts +49 -87
  120. package/src/lib/orchestrator/setup-compatibilities.ts +18 -75
  121. package/src/lib/orchestrator/types.ts +2 -2
  122. package/src/lib/orchestrator/wrap-pass.ts +22 -88
  123. package/src/lib/package-paths.ts +8 -0
  124. package/src/opencode/companion-hooks.ts +64 -5
  125. package/src/opencode/companion-policy.ts +37 -18
  126. package/src/opencode/projected-guidance.ts +4 -1
  127. package/src/opencode/session-prepare.ts +2 -1
  128. package/src/opencode/tui.tsx +4 -2
  129. package/src/runtime/artifact-refusal.ts +63 -0
  130. package/src/runtime/claude-delivery-plan.ts +89 -7
  131. package/src/runtime/commit-attribution-guard.ts +181 -0
  132. package/src/runtime/companion-guidance.ts +15 -3
  133. package/src/runtime/companion-instructions.ts +340 -0
  134. package/src/runtime/error-message.ts +4 -0
  135. package/src/runtime/guidance.ts +0 -17
  136. package/src/runtime/index.ts +5 -0
  137. package/src/runtime/managed-block.ts +117 -0
  138. package/src/runtime/markdown-scan.ts +266 -0
  139. package/src/runtime/policy.ts +16 -15
  140. package/src/runtime/projected-flag.ts +7 -0
  141. package/src/runtime/projected-guidance.ts +1 -0
  142. package/src/runtime/runtime-document-manifest.ts +62 -28
  143. package/src/runtime/secret-guard.ts +148 -0
  144. package/src/runtime/secret-patterns.ts +164 -0
  145. package/src/runtime/secret-scan.ts +90 -0
  146. package/src/runtime/session-kind.ts +13 -0
  147. package/src/runtime/shell-heredoc.ts +81 -0
  148. package/src/runtime/wrap-state.ts +36 -6
  149. package/src/runtime/write-atomic.ts +64 -0
  150. package/src/templates/capabilities/openspec-cap/mate-minimal/schema.yaml +8 -6
  151. package/src/templates/capabilities/openspec-cap/mate-minimal/templates/spec.md +3 -5
  152. package/src/templates/capabilities/openspec-cap/mate-v1/schema.yaml +7 -9
  153. package/src/templates/capabilities/openspec-cap/mate-v1/templates/spec.md +3 -5
  154. package/src/templates/capabilities/openspec-cap/openspec-conventions.yaml +10 -12
  155. package/src/templates/mate-skills/THIRD-PARTY-NOTICES.md +40 -0
  156. package/src/templates/mate-skills/agents/mate-apply-change/SKILL.md +76 -0
  157. package/src/templates/mate-skills/agents/mate-apply-change/references/openspec-apply-change.md +198 -0
  158. package/src/templates/mate-skills/agents/mate-archive-change/SKILL.md +59 -0
  159. package/src/templates/mate-skills/{claude/mate-openspec-backfill → agents/mate-backfill-spec}/SKILL.md +4 -4
  160. package/src/templates/mate-skills/agents/mate-clarify-intent/SKILL.md +95 -0
  161. package/src/templates/mate-skills/agents/mate-explore-change/SKILL.md +50 -0
  162. package/src/templates/mate-skills/agents/mate-explore-change/references/openspec-explore.md +348 -0
  163. package/src/templates/mate-skills/agents/mate-grill-me/SKILL.md +3 -3
  164. package/src/templates/mate-skills/agents/mate-interview-me/SKILL.md +5 -5
  165. package/src/templates/mate-skills/agents/mate-propose-change/SKILL.md +103 -0
  166. package/src/templates/mate-skills/agents/mate-propose-change/references/openspec-propose.md +167 -0
  167. package/src/templates/mate-skills/agents/mate-propose-change/references/openspec-update-change.md +101 -0
  168. package/src/templates/mate-skills/agents/{mate-artifact-publish → mate-publish-artifact}/SKILL.md +91 -23
  169. package/src/templates/mate-skills/agents/{mate-artifact-publish → mate-publish-artifact}/references/openspec.md +4 -3
  170. package/src/templates/mate-skills/agents/mate-show-me/SKILL.md +74 -16
  171. package/src/templates/mate-skills/agents/mate-simplify-code/SKILL.md +74 -68
  172. package/src/templates/mate-skills/claude/mate-apply-change/SKILL.md +76 -0
  173. package/src/templates/mate-skills/claude/mate-apply-change/references/openspec-apply-change.md +198 -0
  174. package/src/templates/mate-skills/claude/mate-archive-change/SKILL.md +59 -0
  175. package/src/templates/mate-skills/{agents/mate-openspec-backfill → claude/mate-backfill-spec}/SKILL.md +4 -4
  176. package/src/templates/mate-skills/claude/mate-clarify-intent/SKILL.md +95 -0
  177. package/src/templates/mate-skills/claude/mate-explore-change/SKILL.md +50 -0
  178. package/src/templates/mate-skills/claude/mate-explore-change/references/openspec-explore.md +348 -0
  179. package/src/templates/mate-skills/claude/mate-grill-me/SKILL.md +3 -3
  180. package/src/templates/mate-skills/claude/mate-interview-me/SKILL.md +5 -5
  181. package/src/templates/mate-skills/claude/mate-propose-change/SKILL.md +103 -0
  182. package/src/templates/mate-skills/claude/mate-propose-change/references/openspec-propose.md +167 -0
  183. package/src/templates/mate-skills/claude/mate-propose-change/references/openspec-update-change.md +101 -0
  184. package/src/templates/mate-skills/claude/{mate-artifact-publish → mate-publish-artifact}/SKILL.md +91 -23
  185. package/src/templates/mate-skills/claude/{mate-artifact-publish → mate-publish-artifact}/references/openspec.md +4 -3
  186. package/src/templates/mate-skills/claude/mate-show-me/SKILL.md +74 -16
  187. package/src/templates/mate-skills/claude/mate-simplify-code/SKILL.md +74 -68
  188. package/src/templates/mate-skills/openspec-references.ts +140 -0
  189. package/src/templates/root/TEMPLATE_AGENTS.md +2 -2
  190. package/src/tools/setup/__snapshots__/runtime-surface-golden.test.ts.snap +3727 -320
  191. package/src/tools/setup/capabilities/context-mode.ts +25 -5
  192. package/src/tools/setup/capabilities/graphify-gate.ts +31 -0
  193. package/src/tools/setup/capabilities/graphify-secrets.ts +255 -0
  194. package/src/tools/setup/capabilities/graphify.ts +12 -0
  195. package/src/tools/setup/capabilities/openspec.ts +273 -270
  196. package/src/tools/setup/capabilities/tokensave.ts +36 -3
  197. package/src/tools/setup/context-services.ts +9 -17
  198. package/src/tools/setup/dynamic-plugins/audiences.ts +7 -39
  199. package/src/tools/setup/dynamic-plugins/declarations.ts +1 -1
  200. package/src/tools/setup/dynamic-plugins/hydrate.ts +4 -7
  201. package/src/tools/setup/dynamic-plugins/install.ts +24 -28
  202. package/src/tools/setup/engine.ts +93 -148
  203. package/src/tools/setup/managed-output.ts +51 -0
  204. package/src/tools/setup/mate.ts +86 -11
  205. package/src/tools/setup/plugin.ts +37 -17
  206. package/src/tools/setup/plugins/gitignore.ts +1 -7
  207. package/src/tools/setup/plugins/guidance.ts +7 -4
  208. package/src/tools/setup/providers/agent-file-sections.ts +4 -2
  209. package/src/tools/setup/providers/claude-attribution.ts +65 -0
  210. package/src/tools/setup/providers/claude-plugin-folders.ts +625 -0
  211. package/src/tools/setup/providers/claude-plugin-hooks.ts +39 -19
  212. package/src/tools/setup/providers/claude.ts +285 -34
  213. package/src/tools/setup/providers/opencode.ts +180 -11
  214. package/src/tools/setup/registry.ts +2 -24
  215. package/src/tools/setup/surface-target.ts +4 -13
  216. package/src/tools/setup/utils.ts +14 -4
  217. package/src/tools/setup.ts +131 -18
  218. package/wrappers/bin/graphify +52 -4
  219. package/wrappers/bin/openspec +33 -9
  220. package/wrappers/lib/graphify-gate.mjs +8 -0
  221. package/opencode-plugin/src/audience-layer.ts +0 -91
  222. package/src/cli/commands/studio/views/workflow/transcript.tsx +0 -190
  223. package/src/runtime/audience-layer.ts +0 -84
  224. package/src/runtime/opencode-audience-layer.ts +0 -176
  225. package/src/tools/setup/audience-isolation.fixture.ts +0 -325
  226. package/src/tools/setup/audience-layer.ts +0 -207
  227. package/src/tools/setup/audience-registrations.ts +0 -52
  228. package/src/tools/setup/contribution-ledger.ts +0 -207
  229. package/src/tools/setup/isolation-diagnostics.ts +0 -139
  230. package/src/tools/setup/runtime-documents.ts +0 -291
  231. package/src/tools/setup/upgrade-sweep.ts +0 -153
@@ -1,8 +1,18 @@
1
+ import {
2
+ DEFAULT_SCHEMA,
3
+ deliveryPath,
4
+ isMateSchema,
5
+ SCHEMA_ARTIFACTS,
6
+ type DeliveryStage,
7
+ type MateSchema,
8
+ type SkillStageId,
9
+ } from "../../../../../lib/delivery/stages";
1
10
  import { CHANGE_PLACEHOLDER, IDEA_PLACEHOLDER } from "../../selection";
11
+ import type { WorkflowTopology } from "../../topology";
2
12
 
3
- export type WorkflowStepKind = "skill" | "review" | "completion" | "optional";
4
- export type WorkflowStepBadge = "optional" | "skill" | "openspec" | "human-in-loop";
5
- export type WorkflowProfile = "mate-v1" | "mate-minimal";
13
+ export type WorkflowStepBadge = "optional" | "skill" | "openspec" | "human-review";
14
+
15
+ export type WorkflowPhaseId = "plan" | "review" | "build" | "ship";
6
16
 
7
17
  export interface WorkflowAlternative {
8
18
  name: string;
@@ -11,327 +21,258 @@ export interface WorkflowAlternative {
11
21
  copyPrompt: string;
12
22
  }
13
23
 
14
- export interface WorkflowStep {
15
- kind: WorkflowStepKind;
16
- id: string;
24
+ export type WorkflowStep = DeliveryStage & {
17
25
  /** The action the stage performs. */
18
26
  what: string;
19
27
  /** The reason the stage is in the sequence at this position. */
20
28
  why: string;
21
- title: string;
22
29
  badges: WorkflowStepBadge[];
23
- sessionBreakBefore?: boolean;
24
- prompt: string;
25
- copyPrompt: string;
30
+ /** Null for a human review: a person performs it, so there is nothing to run. */
31
+ prompt: string | null;
32
+ copyPrompt: string | null;
26
33
  alternatives?: WorkflowAlternative[];
27
- }
34
+ /** The stage's skill when the companion does not report it. */
35
+ missingSkill?: string;
36
+ sessionBreakBefore?: boolean;
37
+ phase: WorkflowPhaseId;
38
+ };
39
+
40
+ /** One step of the stage map: a single stage, or a run of review stages or optional peers. */
41
+ export type WorkflowMapNode =
42
+ | { kind: "stage"; step: WorkflowStep }
43
+ | { kind: "group"; group: "human" | "optional"; steps: WorkflowStep[] };
28
44
 
29
- export interface WorkflowBranch {
30
- profile: WorkflowProfile;
45
+ export interface WorkflowPath {
46
+ schema: string;
31
47
  label: string;
48
+ /** Short tab caption, e.g. `full planning`. */
49
+ tagline: string;
32
50
  description: string;
33
51
  steps: WorkflowStep[];
52
+ map: WorkflowMapNode[];
53
+ reviewsKnown: boolean;
54
+ }
55
+
56
+ export interface WorkflowPlanOptions {
57
+ /** Skills reported for the companion; undefined treats every stage skill as present. */
58
+ skills?: readonly string[];
59
+ topology?: WorkflowTopology | null;
34
60
  }
35
61
 
36
62
  export interface WorkflowPlan {
37
- start: WorkflowStep;
38
- branches: WorkflowBranch[];
39
- shared: WorkflowStep[];
40
- finish: WorkflowStep;
63
+ paths: WorkflowPath[];
41
64
  }
42
65
 
43
- const PRE_EXPLORE_SKILLS = ["mate-interview-me", "mate-grill-me"] as const;
66
+ const CLARIFY_MODES = ["mate-clarify-intent", "mate-interview-me", "mate-grill-me"] as const;
44
67
 
45
- /** The optional post-apply stretch, in presented order, each keyed to the skill that drives it. */
46
- const SHARED_OPTIONAL_SKILLS = {
47
- "show-me": "mate-show-me",
48
- simplify: "mate-simplify-code",
49
- } as const;
68
+ const CLARIFY_DESCRIPTIONS: Record<(typeof CLARIFY_MODES)[number], string> = {
69
+ "mate-clarify-intent":
70
+ "Default. Reads what the request already states and routes to an interview, grilling, or skip, then runs that one mode.",
71
+ "mate-interview-me":
72
+ "Explicit mode: guided, one-question-at-a-time clarification when intent, constraints, or scope still need confirmation.",
73
+ "mate-grill-me":
74
+ "Explicit mode: round-based stress test of a mostly known direction to surface hidden assumptions and risks.",
75
+ };
50
76
 
51
- type SharedOptionalId = keyof typeof SHARED_OPTIONAL_SKILLS;
77
+ const STAGE_TEXT: Record<SkillStageId, { what: string; why: string }> = {
78
+ "clarify-intent": {
79
+ what: "Settles who the change is for, why it is needed, how success is measured, and what binds it, before any artifact exists.",
80
+ why: "Intent gaps are cheapest to close before exploration turns them into documents.",
81
+ },
82
+ explore: {
83
+ what: "Resolves repository evidence, unknowns, and options, with scope taken from the companion context map, and suggests slicing a large feature.",
84
+ why: "It separates uncertainty from the proposal's committed scope.",
85
+ },
86
+ propose: {
87
+ what: "Chooses the schema from the minimal eligibility test, checks slicing when explore agreed none, and writes the change's planning artifacts in one prompt.",
88
+ why: "One prompt writes every artifact; each one is then reviewed on its own.",
89
+ },
90
+ apply: {
91
+ what: "Records the delivery branch, avoiding protected branches, then implements the reviewed tasks.",
92
+ why: "The recorded branch is what archive and publication later wait on.",
93
+ },
94
+ "inspect-outcome": {
95
+ what: "Shows what the applied change did as a rendered diagram and diff in a Mate report.",
96
+ why: "Seeing the result tells the reviewer whether cleanup is warranted.",
97
+ },
98
+ "refine-code": {
99
+ what: "Looks for smaller, clearer code once tests pass, preserving behavior.",
100
+ why: "Cleanup lands before archive, so shipped code and recorded specs agree.",
101
+ },
102
+ archive: {
103
+ what: "Applies the delta specs to the local canonical specs and records the delivery; allowed before the branch merges.",
104
+ why: "A feature's later changes build on canonical specs that already include this one.",
105
+ },
106
+ publish: {
107
+ what: "Commits, tags, and pushes archived changes whose delivery branches are merged, chosen by the developer.",
108
+ why: "Published canonical specs then describe the default branch; unmerged archives wait.",
109
+ },
110
+ };
52
111
 
53
- function stepPrompt(
54
- step: Omit<WorkflowStep, "prompt" | "copyPrompt">,
55
- profile?: WorkflowProfile,
56
- includeChange = true,
57
- ): string {
58
- const changeNote = includeChange ? ` for ${CHANGE_PLACEHOLDER}` : " for";
59
- const ideaNote = includeChange ? ` about ${IDEA_PLACEHOLDER}` : " about";
60
- const schemaNote = profile && profile !== "mate-v1" ? ` (use schema: ${profile})` : "";
112
+ function reviewText(
113
+ artifact: string,
114
+ topology: WorkflowTopology | null | undefined,
115
+ schema: string,
116
+ ) {
117
+ const description =
118
+ topology?.schemaName === schema
119
+ ? topology.artifacts.find((entry) => entry.id === artifact)?.description
120
+ : undefined;
121
+ return {
122
+ what: description ?? `A person reviews the ${artifact} artifact.`,
123
+ why: "Reviews stay with people: Mate never marks them approved.",
124
+ };
125
+ }
61
126
 
62
- switch (step.id) {
63
- case "pre-explore":
64
- return `/mate-interview-me or /mate-grill-me${ideaNote}`;
65
- case "explore":
66
- return `/openspec-explore${schemaNote}${changeNote}`;
67
- case "proposal":
68
- case "propose":
69
- return `/openspec-propose${schemaNote}${changeNote}`;
70
- case "specs":
71
- case "design":
72
- case "tasks":
73
- return `Review ${includeChange ? CHANGE_PLACEHOLDER : ""}/`;
74
- case "apply":
75
- return `/openspec-apply-change${schemaNote}${changeNote}`;
76
- case "show-me":
77
- return `/mate-show-me${changeNote}`;
78
- case "simplify":
79
- return `/mate-simplify-code${changeNote}`;
80
- case "archive":
81
- return `/openspec-archive-change${changeNote}`;
82
- case "finish":
83
- return `/mate-artifact-publish${changeNote}`;
84
- default:
85
- return `Use ${step.title}${schemaNote}${changeNote}`;
86
- }
127
+ function promptFor(stage: DeliveryStage, schema: string): string | null {
128
+ if (stage.kind === "human") return null;
129
+ if (stage.id === "clarify-intent") return `/${stage.skill} about ${IDEA_PLACEHOLDER}`;
130
+ const schemaNote =
131
+ stage.id === "propose" && schema !== DEFAULT_SCHEMA ? ` (use schema: ${schema})` : "";
132
+ return `/${stage.skill}${schemaNote} for ${CHANGE_PLACEHOLDER}`;
87
133
  }
88
134
 
89
- function skillStep(
90
- id: string,
91
- title: string,
92
- what: string,
93
- why: string,
94
- profile?: WorkflowProfile,
95
- ): WorkflowStep {
96
- const step = {
97
- kind: "skill" as const,
98
- id,
99
- title,
100
- what,
101
- why,
102
- badges: ["skill", "openspec"] as WorkflowStepBadge[],
103
- };
104
- return {
105
- ...step,
106
- prompt: stepPrompt(step, profile),
107
- copyPrompt: stepPrompt(step, profile, false),
108
- };
135
+ /** The prompt without its placeholders, ready to be completed after pasting. */
136
+ function copyPromptFor(prompt: string | null): string | null {
137
+ if (!prompt) return prompt;
138
+ return prompt.replace(` ${CHANGE_PLACEHOLDER}`, "").replace(` ${IDEA_PLACEHOLDER}`, "");
109
139
  }
110
140
 
111
- function reviewStep(
112
- id: string,
113
- title: string,
114
- what: string,
115
- why: string,
116
- profile?: WorkflowProfile,
117
- ): WorkflowStep {
118
- const step = {
119
- kind: "review" as const,
120
- id,
121
- title,
122
- what,
123
- why,
124
- badges: ["human-in-loop", "openspec"] as WorkflowStepBadge[],
125
- };
126
- return {
127
- ...step,
128
- prompt: stepPrompt(step, profile),
129
- copyPrompt: stepPrompt(step, profile, false),
130
- };
141
+ function clarifyAlternatives(available: (skill: string) => boolean): WorkflowAlternative[] {
142
+ return CLARIFY_MODES.filter(available).map((name) => ({
143
+ name,
144
+ description: CLARIFY_DESCRIPTIONS[name],
145
+ prompt: `/${name} about ${IDEA_PLACEHOLDER}`,
146
+ copyPrompt: `/${name} about`,
147
+ }));
131
148
  }
132
149
 
133
- function optionalStep(
134
- id: "pre-explore" | SharedOptionalId,
135
- title: string,
136
- what: string,
137
- why: string,
138
- alternatives: WorkflowAlternative[] = [],
139
- ): WorkflowStep {
140
- const step = {
141
- kind: "optional" as const,
142
- id,
143
- title,
144
- what,
145
- why,
146
- badges: ["optional", "skill"] as WorkflowStepBadge[],
147
- };
148
- return {
149
- ...step,
150
- prompt: stepPrompt(step),
151
- copyPrompt: stepPrompt(step, undefined, false),
152
- ...(id === "pre-explore" || alternatives.length > 0 ? { alternatives } : {}),
153
- };
150
+ /** Stages whose skills run an OpenSpec workflow. */
151
+ const OPENSPEC_STAGES: ReadonlySet<SkillStageId> = new Set([
152
+ "explore",
153
+ "propose",
154
+ "apply",
155
+ "archive",
156
+ ]);
157
+
158
+ function badgesFor(stage: DeliveryStage): WorkflowStepBadge[] {
159
+ if (stage.kind === "human") return ["human-review"];
160
+ const badges: WorkflowStepBadge[] = stage.optional ? ["optional", "skill"] : ["skill"];
161
+ if (OPENSPEC_STAGES.has(stage.id)) badges.push("openspec");
162
+ return badges;
163
+ }
164
+
165
+ const SHIPPED_PATH_TEXT: Record<MateSchema, { tagline: string; description: string }> = {
166
+ "mate-v1": {
167
+ tagline: "full planning",
168
+ description: "Full planning path: proposal, specs, design, and tasks.",
169
+ },
170
+ "mate-minimal": {
171
+ tagline: "minimal planning",
172
+ description:
173
+ "Short path: specs and tasks only, for one-Area, low-risk changes with clear intent.",
174
+ },
175
+ };
176
+
177
+ function pathText(schema: string): { tagline: string; description: string } {
178
+ if (isMateSchema(schema)) return SHIPPED_PATH_TEXT[schema];
179
+ return { tagline: "custom schema", description: `Planning path of the ${schema} schema.` };
180
+ }
181
+
182
+ export const PHASE_LABELS: Record<WorkflowPhaseId, string> = {
183
+ plan: "Plan",
184
+ review: "Review",
185
+ build: "Build",
186
+ ship: "Ship",
187
+ };
188
+
189
+ /** Plan precedes reviews, Review holds them, Build is apply plus its optional peers, Ship follows. */
190
+ function phaseOf(stage: DeliveryStage, previous: WorkflowPhaseId | undefined): WorkflowPhaseId {
191
+ if (stage.kind === "human") return "review";
192
+ if (stage.id === "apply") return "build";
193
+ if (previous === "build" && stage.optional) return "build";
194
+ return previous === undefined || previous === "plan" ? "plan" : "ship";
154
195
  }
155
196
 
156
- function withSessionBreak(step: WorkflowStep): WorkflowStep {
157
- return { ...step, sessionBreakBefore: true };
197
+ function groupOf(step: WorkflowStep): "human" | "optional" | null {
198
+ if (step.kind === "human") return "human";
199
+ return step.optional ? "optional" : null;
158
200
  }
159
201
 
160
- export function workflowPlan(
161
- availableSkills: readonly string[] = [
162
- ...PRE_EXPLORE_SKILLS,
163
- ...Object.values(SHARED_OPTIONAL_SKILLS),
164
- ],
165
- ): WorkflowPlan {
166
- const availableSkillSet = new Set(availableSkills);
167
- const preExploreAlternatives: WorkflowAlternative[] = [];
168
- for (const name of PRE_EXPLORE_SKILLS) {
169
- if (!availableSkillSet.has(name)) continue;
170
- preExploreAlternatives.push({
171
- name,
172
- description:
173
- name === "mate-interview-me"
174
- ? "Difference: guided, one-question-at-a-time clarification. Best when: the request is ambiguous and intent, constraints, or scope still need confirmation."
175
- : "Difference: adversarial, round-based review of the full design-tree frontier. Best when: the direction is mostly known but hidden assumptions, risks, and downstream impacts need surfacing.",
176
- prompt: `/${name} about ${IDEA_PLACEHOLDER}`,
177
- copyPrompt: `/${name} about`,
178
- });
202
+ /** Review runs are always one group; optional runs group only when they hold peers. */
203
+ function mapOf(steps: readonly WorkflowStep[]): WorkflowMapNode[] {
204
+ const runs: { group: "human" | "optional" | null; steps: WorkflowStep[] }[] = [];
205
+ for (const step of steps) {
206
+ const group = groupOf(step);
207
+ const run = runs.at(-1);
208
+ if (group && run?.group === group) run.steps.push(step);
209
+ else runs.push({ group, steps: [step] });
179
210
  }
180
- const start = optionalStep(
181
- "pre-explore",
182
- "pre-explore",
183
- "Clarifies intent with confidence and explicit confirmation, or stress-tests the full design frontier before the schema path turns it into artifacts.",
184
- "One conversational choice settles uncertainty early without invoking documentation or implementation work.",
185
- preExploreAlternatives,
211
+ return runs.map((run) =>
212
+ run.group === "human" || (run.group === "optional" && run.steps.length > 1)
213
+ ? { kind: "group", group: run.group, steps: run.steps }
214
+ : { kind: "stage", step: run.steps[0] },
186
215
  );
216
+ }
187
217
 
188
- const branches: WorkflowBranch[] = [
189
- {
190
- profile: "mate-v1",
191
- label: "mate-v1",
192
- description: "Full planning path for changes that need exploration and technical design.",
193
- steps: [
194
- skillStep(
195
- "explore",
196
- "explore",
197
- "Resolves repository evidence, unknowns, options, and direction.",
198
- "It separates uncertainty from the proposal's committed motivation and scope.",
199
- "mate-v1",
200
- ),
201
- skillStep(
202
- "proposal",
203
- "proposal",
204
- "Commits why the change is needed and what is in scope.",
205
- "A written scope gives specs, design, and tasks one decision to build from.",
206
- "mate-v1",
207
- ),
208
- reviewStep(
209
- "specs",
210
- "specs",
211
- "Defines the observable behavior the system must provide.",
212
- "The delta is the contract that will later be merged into the canonical spec.",
213
- "mate-v1",
214
- ),
215
- reviewStep(
216
- "design",
217
- "design",
218
- "Records the technical decisions for implementing the change.",
219
- "Separating how from what exposes risks before implementation starts.",
220
- "mate-v1",
221
- ),
222
- reviewStep(
223
- "tasks",
224
- "tasks",
225
- "Orders the implementation and verification work.",
226
- "Small, ordered tasks turn the agreed artifacts into a trackable build path.",
227
- "mate-v1",
228
- ),
229
- withSessionBreak(
230
- skillStep(
231
- "apply",
232
- "apply",
233
- "Implements the change by working through the generated tasks.",
234
- "Applying only approved tasks keeps code changes inside the agreed scope.",
235
- "mate-v1",
236
- ),
237
- ),
238
- ],
239
- },
240
- {
241
- profile: "mate-minimal",
242
- label: "mate-minimal",
243
- description:
244
- "Short path that explores first, then creates only specs and tasks for one-Area, low-risk changes; switch to mate-v1 when uncertain.",
245
- steps: [
246
- skillStep(
247
- "explore",
248
- "explore",
249
- "Resolves repository evidence, unknowns, options, and direction without adding a planning artifact.",
250
- "It gives the minimal proposal step an agreed direction while keeping the output to specs and tasks.",
251
- "mate-minimal",
252
- ),
253
- skillStep(
254
- "propose",
255
- "propose",
256
- "Creates the concise, parser-compatible specs and tasks from the explored direction.",
257
- "OpenSpec turns the agreed direction into only the minimal profile's specs and tasks; proposal and design artifacts are intentionally omitted.",
258
- "mate-minimal",
259
- ),
260
- reviewStep(
261
- "specs",
262
- "specs",
263
- "Defines the behavior change as a concise, parser-compatible user story.",
264
- "Reviewing the generated delta confirms the small change before tasks are applied.",
265
- "mate-minimal",
266
- ),
267
- reviewStep(
268
- "tasks",
269
- "tasks",
270
- "Orders the implementation and verification work.",
271
- "The checklist keeps a deliberately small change concrete and reviewable.",
272
- "mate-minimal",
273
- ),
274
- withSessionBreak(
275
- skillStep(
276
- "apply",
277
- "apply",
278
- "Implements the change by working through the generated tasks.",
279
- "Applying only the short approved checklist keeps the low-risk boundary intact.",
280
- "mate-minimal",
281
- ),
282
- ),
283
- ],
284
- },
285
- ];
218
+ export function buildPath(
219
+ schema: string,
220
+ artifacts: readonly string[] | undefined,
221
+ options: WorkflowPlanOptions,
222
+ ): WorkflowPath {
223
+ const reported = options.skills ? new Set(options.skills) : null;
224
+ const available = (skill: string): boolean => !reported || reported.has(skill);
225
+ const contract = deliveryPath(artifacts);
286
226
 
287
- const showMe = optionalStep(
288
- "show-me",
289
- "mate show me",
290
- "Shows what the applied change did as a rendered diagram and a colorized diff, delivered as a Mate report.",
291
- "Seeing the resulting structure and the actual diff is what tells the reviewer whether cleanup is warranted.",
292
- );
293
- const simplify = optionalStep(
294
- "simplify",
295
- "mate simplify code",
296
- "Looks for smaller, clearer code once the tests pass, preserving behavior and rerunning the relevant tests.",
297
- "Cleanup lands before the specs are archived, so shipped code and recorded specs stay aligned.",
298
- );
299
- /** Peers, not prerequisites: an unreported skill drops its step and the rest keep their order. */
300
- const optionalReview: WorkflowStep[] = [];
301
- for (const step of [showMe, simplify]) {
302
- if (!availableSkillSet.has(SHARED_OPTIONAL_SKILLS[step.id as SharedOptionalId])) continue;
303
- optionalReview.push(optionalReview.length === 0 ? withSessionBreak(step) : step);
227
+ const steps: WorkflowStep[] = [];
228
+ for (const stage of contract.stages) {
229
+ const text =
230
+ stage.kind === "human"
231
+ ? reviewText(stage.artifact, options.topology, schema)
232
+ : STAGE_TEXT[stage.id];
233
+ const prompt = promptFor(stage, schema);
234
+ const previous = steps.at(-1);
235
+ const step: WorkflowStep = {
236
+ ...stage,
237
+ what: text.what,
238
+ why: text.why,
239
+ badges: badgesFor(stage),
240
+ prompt,
241
+ copyPrompt: copyPromptFor(prompt),
242
+ phase: phaseOf(stage, previous?.phase),
243
+ };
244
+ if (stage.id === "clarify-intent") step.alternatives = clarifyAlternatives(available);
245
+ if (stage.kind === "skill" && !available(stage.skill)) step.missingSkill = stage.skill;
246
+ // A new session starts at apply; the optional peers after it reuse its context.
247
+ if (stage.id === "apply") step.sessionBreakBefore = true;
248
+ steps.push(step);
304
249
  }
305
- const archive = skillStep(
306
- "archive",
307
- "openspec archive change",
308
- "Archives the verified change and its approved artifacts, applying its delta specs to the canonical specs.",
309
- "Archiving validates the change, requires it to be complete, and is the precondition publishing refuses without.",
310
- );
311
- const finishStep = {
312
- kind: "completion" as const,
313
- id: "finish",
314
- title: "mate artifact publish",
315
- what: "Presents the already-archived changes and the drifted canonical specs as two selectable tables, then runs `mate artifact publish` for exactly what the developer picks — announcing that each selection is committed, tagged, and pushed to the companion Git repository before the first one runs.",
316
- why: "It is the only sanctioned completion; a hand-committed publication leaves the anchor untagged and unrevertable, and a drifted spec shipped under an old archive's tag misfiles today's edit under a change that did not produce it.",
317
- badges: ["skill"] as WorkflowStepBadge[],
318
- };
319
250
 
320
251
  return {
321
- start,
322
- branches,
323
- shared: [...optionalReview, archive],
324
- finish: {
325
- ...finishStep,
326
- prompt: stepPrompt(finishStep),
327
- copyPrompt: stepPrompt(finishStep, undefined, false),
328
- },
252
+ schema,
253
+ label: schema,
254
+ ...pathText(schema),
255
+ steps,
256
+ map: mapOf(steps),
257
+ reviewsKnown: contract.reviewsKnown,
329
258
  };
330
259
  }
331
260
 
332
- export function workflowSteps(profile: WorkflowProfile = "mate-v1"): WorkflowStep[] {
333
- const plan = workflowPlan();
334
- const branch = plan.branches.find((entry) => entry.profile === profile);
335
- if (!branch) return [plan.start, ...plan.shared, plan.finish];
336
- return [plan.start, ...branch.steps, ...plan.shared, plan.finish];
261
+ function schemaArtifacts(
262
+ schema: string,
263
+ topology: WorkflowTopology | null | undefined,
264
+ ): readonly string[] | undefined {
265
+ if (topology?.schemaName === schema) return topology.artifacts.map((artifact) => artifact.id);
266
+ return isMateSchema(schema) ? SCHEMA_ARTIFACTS[schema] : undefined;
267
+ }
268
+
269
+ /** The Delivery Path Studio shows: a path per schema Mate ships, or the served schema's alone. */
270
+ export function workflowPlan(options: WorkflowPlanOptions = {}): WorkflowPlan {
271
+ const served = options.topology?.schemaName;
272
+ const schemas = served && !isMateSchema(served) ? [served] : Object.keys(SCHEMA_ARTIFACTS);
273
+ return {
274
+ paths: schemas.map((schema) =>
275
+ buildPath(schema, schemaArtifacts(schema, options.topology), options),
276
+ ),
277
+ };
337
278
  }
@@ -4,7 +4,6 @@ import { FRAMEWORK_NAME } from "../../framework";
4
4
  import { findRepoLocalLinkedRepository } from "../../lib/orchestrator/repository-link";
5
5
  import type { ProjectionChangedPaths } from "../../lib/orchestrator/projection-types";
6
6
  import { attendedLockOptions } from "../../lib/orchestrator/companion-git-sync";
7
- import { readCompanionConfig } from "../../lib/orchestrator/config-store";
8
7
  import { runWrapPass, type WrapPassResult } from "../../lib/orchestrator/wrap-pass";
9
8
  import { withCompanionLock } from "../../runtime/companion-lock";
10
9
  import { WRAP_RUNTIMES } from "../../runtime/wrap-state";
@@ -33,29 +32,12 @@ function reportChangedPaths(changed: ProjectionChangedPaths): void {
33
32
 
34
33
  /**
35
34
  * `--companion` is consumed by the dispatch gate before the command runs, so it
36
- * is parsed there too. `--audience` names the audience the checkout's bare
37
- * sessions get beside base.
35
+ * is parsed there too; this only rejects anything else.
38
36
  */
39
- export function parseWrapArgs(
40
- argv: string[],
41
- ): { companion?: string; audience?: string } | { error: string } {
37
+ export function parseWrapArgs(argv: string[]): { companion?: string } | { error: string } {
42
38
  let companion: string | undefined;
43
- let audience: string | undefined;
44
39
  for (let index = 0; index < argv.length; index += 1) {
45
40
  const arg = argv[index]!;
46
- if (arg === "--audience") {
47
- const value = argv[index + 1];
48
- if (!value || value.startsWith("--")) return { error: "--audience requires a name" };
49
- audience = value;
50
- index += 1;
51
- continue;
52
- }
53
- if (arg.startsWith("--audience=")) {
54
- const value = arg.slice("--audience=".length);
55
- if (!value) return { error: "--audience requires a name" };
56
- audience = value;
57
- continue;
58
- }
59
41
  if (arg === "--companion") {
60
42
  const value = argv[index + 1];
61
43
  if (!value || value.startsWith("--")) return { error: "--companion requires a path" };
@@ -71,10 +53,7 @@ export function parseWrapArgs(
71
53
  }
72
54
  return { error: `unknown wrap option: ${arg}` };
73
55
  }
74
- return {
75
- ...(companion === undefined ? {} : { companion }),
76
- ...(audience === undefined ? {} : { audience }),
77
- };
56
+ return companion === undefined ? {} : { companion };
78
57
  }
79
58
 
80
59
  /**
@@ -128,12 +107,8 @@ export async function runWrapCommand(
128
107
  }
129
108
 
130
109
  /** Serialized with Session Prepare and every sync, which rewrite the same manifest. */
131
- /**
132
- * The flag is the only way to give a checkout's bare sessions an audience;
133
- * without it the wrap is base and any recorded audience is withdrawn.
134
- */
135
110
  const locked = await withCompanionLock(companionPath, attendedLockOptions(companionPath), () =>
136
- wrap(companionPath, repository, { audience: parsed.audience ?? null }),
111
+ wrap(companionPath, repository),
137
112
  );
138
113
  const result: WrapPassResult = locked.acquired
139
114
  ? locked.value
@@ -150,11 +125,11 @@ export async function runWrapCommand(
150
125
  : `Already wrapped: ${result.projectionRoot}`,
151
126
  );
152
127
  console.log(` Companion: ${result.companionPath}`);
153
- if (result.audience) console.log(` Audience: ${result.audience}`);
154
128
  for (const document of result.documents) {
155
129
  console.log(` Runtime: ${document}`);
156
130
  }
157
- for (const warning of result.warnings) {
131
+ const publication = result.claudePluginFolders?.publication;
132
+ for (const warning of [...result.warnings, ...(publication?.warnings ?? [])]) {
158
133
  console.error(`${FRAMEWORK_NAME}: warning: ${warning}`);
159
134
  }
160
135
  reportChangedPaths(result.changed);
@@ -166,18 +141,10 @@ export async function runWrapCommand(
166
141
  console.log(
167
142
  `Start sessions yourself (${plain}) or through ${managed}; both work here. \`${FRAMEWORK_NAME} unwrap\` reverses this.`,
168
143
  );
169
- if (runtimes.has("claude") && (await contextModeEnabled(result.companionPath))) {
144
+ if (!runtimes.has("claude")) return;
145
+ for (const unpublished of publication?.unpublished ?? []) {
170
146
  console.log(
171
- `Note: context-mode loads only in sessions started with \`${FRAMEWORK_NAME} claude\`.`,
147
+ `Note: ${unpublished.capability}'s Claude plugin folder ${unpublished.path} ${unpublished.detail}, so it loads only in sessions started with \`${FRAMEWORK_NAME} claude\`.`,
172
148
  );
173
149
  }
174
150
  }
175
-
176
- async function contextModeEnabled(companionPath: string): Promise<boolean> {
177
- try {
178
- const config = await readCompanionConfig(companionPath);
179
- return (config.capabilities ?? []).some((capability) => capability.name === "context-mode");
180
- } catch {
181
- return false;
182
- }
183
- }