@uniqbit/mate-core 0.18.5 → 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 (209) hide show
  1. package/claude-plugin/hooks/companion-instructions.mjs +9 -0
  2. package/claude-plugin/hooks/hooks.json +12 -2
  3. package/package.json +10 -2
  4. package/src/brand/art.generated.ts +69 -0
  5. package/src/brand/index.ts +12 -0
  6. package/src/brand/palette.ts +19 -0
  7. package/src/brand/svg.generated.ts +8 -0
  8. package/src/brand/terminal.ts +83 -0
  9. package/src/brand/types.ts +33 -0
  10. package/src/cli/commands/artifact/artifact.ts +8 -0
  11. package/src/cli/commands/artifact/delivery/archive.ts +117 -0
  12. package/src/cli/commands/artifact/delivery/branch-flags.ts +55 -0
  13. package/src/cli/commands/artifact/delivery/environment.ts +104 -0
  14. package/src/cli/commands/artifact/delivery/link.ts +154 -0
  15. package/src/cli/commands/artifact/delivery/output.ts +58 -0
  16. package/src/cli/commands/artifact/finish/command.ts +33 -3
  17. package/src/cli/commands/artifact/finish/engine.ts +47 -3
  18. package/src/cli/commands/artifact/finish/finisher.ts +10 -0
  19. package/src/cli/commands/artifact/finish/openspec.ts +177 -138
  20. package/src/cli/commands/artifact/finish/secret-scan.ts +43 -0
  21. package/src/cli/commands/artifact/pending/command.ts +31 -3
  22. package/src/cli/commands/artifact/pending/discovery.ts +21 -57
  23. package/src/cli/commands/cap/graphify.ts +52 -3
  24. package/src/cli/commands/cap/index-cmd.ts +8 -1
  25. package/src/cli/commands/cap/openspec.ts +13 -25
  26. package/src/cli/commands/companion/open.ts +56 -39
  27. package/src/cli/commands/companion/update.ts +75 -42
  28. package/src/cli/commands/doctor.ts +176 -3
  29. package/src/cli/commands/plugin/install.ts +9 -4
  30. package/src/cli/commands/plugin/plugin.ts +3 -2
  31. package/src/cli/commands/report/adapter.ts +11 -2
  32. package/src/cli/commands/report/contract.ts +21 -0
  33. package/src/cli/commands/report/highlight.ts +34 -4
  34. package/src/cli/commands/report/markdown.ts +110 -0
  35. package/src/cli/commands/report/palette.ts +118 -0
  36. package/src/cli/commands/report/renderer.ts +456 -153
  37. package/src/cli/commands/report/types.ts +16 -0
  38. package/src/cli/commands/setup.ts +7 -15
  39. package/src/cli/commands/studio/client-asset.ts +16 -0
  40. package/src/cli/commands/studio/openspec-cli.ts +10 -58
  41. package/src/cli/commands/studio/routes.ts +2 -0
  42. package/src/cli/commands/studio/selection.ts +19 -1
  43. package/src/cli/commands/studio/server.ts +42 -1
  44. package/src/cli/commands/studio/views/client.ts +6 -616
  45. package/src/cli/commands/studio/views/dashboard/changes.tsx +5 -10
  46. package/src/cli/commands/studio/views/document.tsx +137 -69
  47. package/src/cli/commands/studio/views/markdown.tsx +250 -0
  48. package/src/cli/commands/studio/views/palette.ts +134 -0
  49. package/src/cli/commands/studio/views/reports/index.tsx +2 -7
  50. package/src/cli/commands/studio/views/skills/index.tsx +43 -62
  51. package/src/cli/commands/studio/views/specs/index.tsx +7 -15
  52. package/src/cli/commands/studio/views/studio-client/actions.ts +51 -0
  53. package/src/cli/commands/studio/views/studio-client/companion.ts +13 -0
  54. package/src/cli/commands/studio/views/studio-client/index.ts +32 -0
  55. package/src/cli/commands/studio/views/studio-client/keys.ts +3 -0
  56. package/src/cli/commands/studio/views/studio-client/main.ts +3 -0
  57. package/src/cli/commands/studio/views/studio-client/page.ts +146 -0
  58. package/src/cli/commands/studio/views/studio-client/panels.ts +57 -0
  59. package/src/cli/commands/studio/views/studio-client/reports.ts +122 -0
  60. package/src/cli/commands/studio/views/studio-client/theme.ts +63 -0
  61. package/src/cli/commands/studio/views/studio-client/vault-editor.ts +166 -0
  62. package/src/cli/commands/studio/views/studio-client/vault-pane.ts +202 -0
  63. package/src/cli/commands/studio/views/studio-client/vault-tree.ts +258 -0
  64. package/src/cli/commands/studio/views/studio-client.generated.ts +2 -0
  65. package/src/cli/commands/studio/views/styles.ts +217 -206
  66. package/src/cli/commands/studio/views/vault/state.ts +3 -5
  67. package/src/cli/commands/studio/views/workflow/delivery-path.tsx +370 -0
  68. package/src/cli/commands/studio/views/workflow/graph.ts +220 -0
  69. package/src/cli/commands/studio/views/workflow/index.tsx +2 -3
  70. package/src/cli/commands/studio/views/workflow/steps.ts +228 -287
  71. package/src/cli/commands/wrap.ts +5 -13
  72. package/src/cli/main.ts +4 -1
  73. package/src/cli/parse-flags.ts +25 -7
  74. package/src/cli/setup-selector.tsx +38 -162
  75. package/src/cli/usage.ts +4 -0
  76. package/src/hooks/companion-instructions.ts +190 -0
  77. package/src/hooks/hook-io.ts +63 -0
  78. package/src/hooks/session-banner.ts +21 -29
  79. package/src/hooks/session-guidance.ts +9 -17
  80. package/src/hooks/session-prepare.ts +115 -28
  81. package/src/hooks/validate-artifact-path.ts +82 -24
  82. package/src/index.ts +4 -0
  83. package/src/lib/change-layout.ts +154 -0
  84. package/src/lib/components/wizard-header.tsx +3 -1
  85. package/src/lib/context-mode-package.ts +24 -11
  86. package/src/lib/delivery/archive-change.ts +154 -0
  87. package/src/lib/delivery/branches.ts +108 -0
  88. package/src/lib/delivery/environment.ts +29 -0
  89. package/src/lib/delivery/forge.ts +279 -0
  90. package/src/lib/delivery/overlap.ts +202 -0
  91. package/src/lib/delivery/record-delivery.ts +196 -0
  92. package/src/lib/delivery/record.ts +343 -0
  93. package/src/lib/delivery/refresh.ts +174 -0
  94. package/src/lib/delivery/repository-access.ts +97 -0
  95. package/src/lib/delivery/stages.ts +111 -0
  96. package/src/lib/delivery/token.ts +58 -0
  97. package/src/lib/frontmatter.ts +18 -0
  98. package/src/lib/opencode-plugin-package.ts +5 -5
  99. package/src/lib/openspec-cli.ts +116 -0
  100. package/src/lib/openspec-scopes.ts +86 -0
  101. package/src/lib/orchestrator/adapters/claude-args.ts +50 -0
  102. package/src/lib/orchestrator/adapters/claude.ts +48 -40
  103. package/src/lib/orchestrator/adapters/opencode.ts +2 -0
  104. package/src/lib/orchestrator/editor.ts +9 -9
  105. package/src/lib/orchestrator/openspec-store.ts +14 -54
  106. package/src/lib/orchestrator/projection-claude-commands.ts +36 -0
  107. package/src/lib/orchestrator/projection-claude-links.ts +159 -0
  108. package/src/lib/orchestrator/projection-claude-skills.ts +19 -137
  109. package/src/lib/orchestrator/projection-entries.ts +45 -14
  110. package/src/lib/orchestrator/projection-runtime-documents.ts +15 -6
  111. package/src/lib/orchestrator/projection-types.ts +3 -0
  112. package/src/lib/orchestrator/refresh-working-claude-links.ts +87 -0
  113. package/src/lib/orchestrator/session-prepare.ts +45 -5
  114. package/src/lib/orchestrator/setup-compatibilities.ts +18 -75
  115. package/src/lib/orchestrator/types.ts +2 -2
  116. package/src/lib/orchestrator/wrap-pass.ts +22 -1
  117. package/src/lib/package-paths.ts +8 -0
  118. package/src/opencode/companion-hooks.ts +64 -5
  119. package/src/opencode/companion-policy.ts +37 -18
  120. package/src/opencode/projected-guidance.ts +4 -1
  121. package/src/opencode/session-prepare.ts +2 -1
  122. package/src/opencode/tui.tsx +4 -2
  123. package/src/runtime/artifact-refusal.ts +63 -0
  124. package/src/runtime/claude-delivery-plan.ts +89 -7
  125. package/src/runtime/commit-attribution-guard.ts +181 -0
  126. package/src/runtime/companion-guidance.ts +15 -3
  127. package/src/runtime/companion-instructions.ts +340 -0
  128. package/src/runtime/error-message.ts +4 -0
  129. package/src/runtime/index.ts +5 -0
  130. package/src/runtime/managed-block.ts +117 -0
  131. package/src/runtime/markdown-scan.ts +266 -0
  132. package/src/runtime/policy.ts +15 -1
  133. package/src/runtime/projected-flag.ts +7 -0
  134. package/src/runtime/projected-guidance.ts +1 -0
  135. package/src/runtime/runtime-document-manifest.ts +62 -11
  136. package/src/runtime/secret-guard.ts +148 -0
  137. package/src/runtime/secret-patterns.ts +164 -0
  138. package/src/runtime/secret-scan.ts +90 -0
  139. package/src/runtime/session-kind.ts +13 -0
  140. package/src/runtime/shell-heredoc.ts +81 -0
  141. package/src/runtime/wrap-state.ts +36 -2
  142. package/src/runtime/write-atomic.ts +64 -0
  143. package/src/templates/capabilities/openspec-cap/mate-minimal/schema.yaml +8 -6
  144. package/src/templates/capabilities/openspec-cap/mate-minimal/templates/spec.md +3 -5
  145. package/src/templates/capabilities/openspec-cap/mate-v1/schema.yaml +7 -9
  146. package/src/templates/capabilities/openspec-cap/mate-v1/templates/spec.md +3 -5
  147. package/src/templates/capabilities/openspec-cap/openspec-conventions.yaml +10 -12
  148. package/src/templates/mate-skills/THIRD-PARTY-NOTICES.md +40 -0
  149. package/src/templates/mate-skills/agents/mate-apply-change/SKILL.md +76 -0
  150. package/src/templates/mate-skills/agents/mate-apply-change/references/openspec-apply-change.md +198 -0
  151. package/src/templates/mate-skills/agents/mate-archive-change/SKILL.md +59 -0
  152. package/src/templates/mate-skills/{claude/mate-openspec-backfill → agents/mate-backfill-spec}/SKILL.md +4 -4
  153. package/src/templates/mate-skills/agents/mate-clarify-intent/SKILL.md +95 -0
  154. package/src/templates/mate-skills/agents/mate-explore-change/SKILL.md +50 -0
  155. package/src/templates/mate-skills/agents/mate-explore-change/references/openspec-explore.md +348 -0
  156. package/src/templates/mate-skills/agents/mate-grill-me/SKILL.md +3 -3
  157. package/src/templates/mate-skills/agents/mate-interview-me/SKILL.md +5 -5
  158. package/src/templates/mate-skills/agents/mate-propose-change/SKILL.md +103 -0
  159. package/src/templates/mate-skills/agents/mate-propose-change/references/openspec-propose.md +167 -0
  160. package/src/templates/mate-skills/agents/mate-propose-change/references/openspec-update-change.md +101 -0
  161. package/src/templates/mate-skills/{claude/mate-artifact-publish → agents/mate-publish-artifact}/SKILL.md +91 -23
  162. package/src/templates/mate-skills/agents/{mate-artifact-publish → mate-publish-artifact}/references/openspec.md +4 -3
  163. package/src/templates/mate-skills/agents/mate-show-me/SKILL.md +74 -16
  164. package/src/templates/mate-skills/agents/mate-simplify-code/SKILL.md +74 -68
  165. package/src/templates/mate-skills/claude/mate-apply-change/SKILL.md +76 -0
  166. package/src/templates/mate-skills/claude/mate-apply-change/references/openspec-apply-change.md +198 -0
  167. package/src/templates/mate-skills/claude/mate-archive-change/SKILL.md +59 -0
  168. package/src/templates/mate-skills/{agents/mate-openspec-backfill → claude/mate-backfill-spec}/SKILL.md +4 -4
  169. package/src/templates/mate-skills/claude/mate-clarify-intent/SKILL.md +95 -0
  170. package/src/templates/mate-skills/claude/mate-explore-change/SKILL.md +50 -0
  171. package/src/templates/mate-skills/claude/mate-explore-change/references/openspec-explore.md +348 -0
  172. package/src/templates/mate-skills/claude/mate-grill-me/SKILL.md +3 -3
  173. package/src/templates/mate-skills/claude/mate-interview-me/SKILL.md +5 -5
  174. package/src/templates/mate-skills/claude/mate-propose-change/SKILL.md +103 -0
  175. package/src/templates/mate-skills/claude/mate-propose-change/references/openspec-propose.md +167 -0
  176. package/src/templates/mate-skills/claude/mate-propose-change/references/openspec-update-change.md +101 -0
  177. package/src/templates/mate-skills/{agents/mate-artifact-publish → claude/mate-publish-artifact}/SKILL.md +91 -23
  178. package/src/templates/mate-skills/claude/{mate-artifact-publish → mate-publish-artifact}/references/openspec.md +4 -3
  179. package/src/templates/mate-skills/claude/mate-show-me/SKILL.md +74 -16
  180. package/src/templates/mate-skills/claude/mate-simplify-code/SKILL.md +74 -68
  181. package/src/templates/mate-skills/openspec-references.ts +140 -0
  182. package/src/templates/root/TEMPLATE_AGENTS.md +2 -2
  183. package/src/tools/setup/__snapshots__/runtime-surface-golden.test.ts.snap +3727 -320
  184. package/src/tools/setup/capabilities/context-mode.ts +25 -5
  185. package/src/tools/setup/capabilities/graphify-gate.ts +31 -0
  186. package/src/tools/setup/capabilities/graphify-secrets.ts +255 -0
  187. package/src/tools/setup/capabilities/graphify.ts +12 -0
  188. package/src/tools/setup/capabilities/openspec.ts +151 -270
  189. package/src/tools/setup/capabilities/tokensave.ts +36 -3
  190. package/src/tools/setup/context-services.ts +9 -17
  191. package/src/tools/setup/dynamic-plugins/install.ts +24 -28
  192. package/src/tools/setup/engine.ts +97 -21
  193. package/src/tools/setup/managed-output.ts +51 -0
  194. package/src/tools/setup/mate.ts +86 -11
  195. package/src/tools/setup/plugin.ts +35 -0
  196. package/src/tools/setup/plugins/guidance.ts +7 -4
  197. package/src/tools/setup/providers/agent-file-sections.ts +4 -2
  198. package/src/tools/setup/providers/claude-attribution.ts +65 -0
  199. package/src/tools/setup/providers/claude-plugin-folders.ts +625 -0
  200. package/src/tools/setup/providers/claude-plugin-hooks.ts +39 -19
  201. package/src/tools/setup/providers/claude.ts +284 -8
  202. package/src/tools/setup/providers/opencode.ts +180 -1
  203. package/src/tools/setup/utils.ts +14 -4
  204. package/src/tools/setup.ts +126 -10
  205. package/wrappers/bin/graphify +52 -4
  206. package/wrappers/bin/openspec +33 -9
  207. package/wrappers/lib/graphify-gate.mjs +8 -0
  208. package/src/cli/commands/studio/views/workflow/transcript.tsx +0 -190
  209. package/src/tools/setup/runtime-documents.ts +0 -291
@@ -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";
@@ -129,7 +128,8 @@ export async function runWrapCommand(
129
128
  for (const document of result.documents) {
130
129
  console.log(` Runtime: ${document}`);
131
130
  }
132
- for (const warning of result.warnings) {
131
+ const publication = result.claudePluginFolders?.publication;
132
+ for (const warning of [...result.warnings, ...(publication?.warnings ?? [])]) {
133
133
  console.error(`${FRAMEWORK_NAME}: warning: ${warning}`);
134
134
  }
135
135
  reportChangedPaths(result.changed);
@@ -141,18 +141,10 @@ export async function runWrapCommand(
141
141
  console.log(
142
142
  `Start sessions yourself (${plain}) or through ${managed}; both work here. \`${FRAMEWORK_NAME} unwrap\` reverses this.`,
143
143
  );
144
- if (runtimes.has("claude") && (await contextModeEnabled(result.companionPath))) {
144
+ if (!runtimes.has("claude")) return;
145
+ for (const unpublished of publication?.unpublished ?? []) {
145
146
  console.log(
146
- `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\`.`,
147
148
  );
148
149
  }
149
150
  }
150
-
151
- async function contextModeEnabled(companionPath: string): Promise<boolean> {
152
- try {
153
- const config = await readCompanionConfig(companionPath);
154
- return (config.capabilities ?? []).some((capability) => capability.name === "context-mode");
155
- } catch {
156
- return false;
157
- }
158
- }
package/src/cli/main.ts CHANGED
@@ -209,8 +209,11 @@ export async function main(argv = process.argv, deps: MainDeps = mainDeps): Prom
209
209
  case "prepare":
210
210
  if (!(await gate({ updateGuard: true }))) return;
211
211
  break;
212
- // open/tui/sync/update consume a companion context.
212
+ // open lists every Linked Companion itself instead of selecting one.
213
213
  case "open":
214
+ if (!(await gate({ updateGuard: true, notHubRoot: true }))) return;
215
+ break;
216
+ // tui/sync/update consume a companion context.
214
217
  case "tui":
215
218
  case "sync":
216
219
  case "update":