@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.
- package/claude-plugin/hooks/companion-instructions.mjs +9 -0
- package/claude-plugin/hooks/hooks.json +12 -2
- package/package.json +10 -2
- package/src/brand/art.generated.ts +69 -0
- package/src/brand/index.ts +12 -0
- package/src/brand/palette.ts +19 -0
- package/src/brand/svg.generated.ts +8 -0
- package/src/brand/terminal.ts +83 -0
- package/src/brand/types.ts +33 -0
- package/src/cli/commands/artifact/artifact.ts +8 -0
- package/src/cli/commands/artifact/delivery/archive.ts +117 -0
- package/src/cli/commands/artifact/delivery/branch-flags.ts +55 -0
- package/src/cli/commands/artifact/delivery/environment.ts +104 -0
- package/src/cli/commands/artifact/delivery/link.ts +154 -0
- package/src/cli/commands/artifact/delivery/output.ts +58 -0
- package/src/cli/commands/artifact/finish/command.ts +33 -3
- package/src/cli/commands/artifact/finish/engine.ts +47 -3
- package/src/cli/commands/artifact/finish/finisher.ts +10 -0
- package/src/cli/commands/artifact/finish/openspec.ts +177 -138
- package/src/cli/commands/artifact/finish/secret-scan.ts +43 -0
- package/src/cli/commands/artifact/pending/command.ts +31 -3
- package/src/cli/commands/artifact/pending/discovery.ts +21 -57
- package/src/cli/commands/cap/graphify.ts +52 -3
- package/src/cli/commands/cap/index-cmd.ts +8 -1
- package/src/cli/commands/cap/openspec.ts +13 -25
- package/src/cli/commands/companion/open.ts +56 -39
- package/src/cli/commands/companion/update.ts +75 -42
- package/src/cli/commands/doctor.ts +176 -3
- package/src/cli/commands/plugin/install.ts +9 -4
- package/src/cli/commands/plugin/plugin.ts +3 -2
- package/src/cli/commands/report/adapter.ts +11 -2
- package/src/cli/commands/report/contract.ts +21 -0
- package/src/cli/commands/report/highlight.ts +34 -4
- package/src/cli/commands/report/markdown.ts +110 -0
- package/src/cli/commands/report/palette.ts +118 -0
- package/src/cli/commands/report/renderer.ts +456 -153
- package/src/cli/commands/report/types.ts +16 -0
- package/src/cli/commands/setup.ts +7 -15
- package/src/cli/commands/studio/client-asset.ts +16 -0
- package/src/cli/commands/studio/openspec-cli.ts +10 -58
- package/src/cli/commands/studio/routes.ts +2 -0
- package/src/cli/commands/studio/selection.ts +19 -1
- package/src/cli/commands/studio/server.ts +42 -1
- package/src/cli/commands/studio/views/client.ts +6 -616
- package/src/cli/commands/studio/views/dashboard/changes.tsx +5 -10
- package/src/cli/commands/studio/views/document.tsx +137 -69
- package/src/cli/commands/studio/views/markdown.tsx +250 -0
- package/src/cli/commands/studio/views/palette.ts +134 -0
- package/src/cli/commands/studio/views/reports/index.tsx +2 -7
- package/src/cli/commands/studio/views/skills/index.tsx +43 -62
- package/src/cli/commands/studio/views/specs/index.tsx +7 -15
- package/src/cli/commands/studio/views/studio-client/actions.ts +51 -0
- package/src/cli/commands/studio/views/studio-client/companion.ts +13 -0
- package/src/cli/commands/studio/views/studio-client/index.ts +32 -0
- package/src/cli/commands/studio/views/studio-client/keys.ts +3 -0
- package/src/cli/commands/studio/views/studio-client/main.ts +3 -0
- package/src/cli/commands/studio/views/studio-client/page.ts +146 -0
- package/src/cli/commands/studio/views/studio-client/panels.ts +57 -0
- package/src/cli/commands/studio/views/studio-client/reports.ts +122 -0
- package/src/cli/commands/studio/views/studio-client/theme.ts +63 -0
- package/src/cli/commands/studio/views/studio-client/vault-editor.ts +166 -0
- package/src/cli/commands/studio/views/studio-client/vault-pane.ts +202 -0
- package/src/cli/commands/studio/views/studio-client/vault-tree.ts +258 -0
- package/src/cli/commands/studio/views/studio-client.generated.ts +2 -0
- package/src/cli/commands/studio/views/styles.ts +217 -206
- package/src/cli/commands/studio/views/vault/state.ts +3 -5
- package/src/cli/commands/studio/views/workflow/delivery-path.tsx +370 -0
- package/src/cli/commands/studio/views/workflow/graph.ts +220 -0
- package/src/cli/commands/studio/views/workflow/index.tsx +2 -3
- package/src/cli/commands/studio/views/workflow/steps.ts +228 -287
- package/src/cli/commands/wrap.ts +5 -13
- package/src/cli/main.ts +4 -1
- package/src/cli/parse-flags.ts +25 -7
- package/src/cli/setup-selector.tsx +38 -162
- package/src/cli/usage.ts +4 -0
- package/src/hooks/companion-instructions.ts +190 -0
- package/src/hooks/hook-io.ts +63 -0
- package/src/hooks/session-banner.ts +21 -29
- package/src/hooks/session-guidance.ts +9 -17
- package/src/hooks/session-prepare.ts +115 -28
- package/src/hooks/validate-artifact-path.ts +82 -24
- package/src/index.ts +4 -0
- package/src/lib/change-layout.ts +154 -0
- package/src/lib/components/wizard-header.tsx +3 -1
- package/src/lib/context-mode-package.ts +24 -11
- package/src/lib/delivery/archive-change.ts +154 -0
- package/src/lib/delivery/branches.ts +108 -0
- package/src/lib/delivery/environment.ts +29 -0
- package/src/lib/delivery/forge.ts +279 -0
- package/src/lib/delivery/overlap.ts +202 -0
- package/src/lib/delivery/record-delivery.ts +196 -0
- package/src/lib/delivery/record.ts +343 -0
- package/src/lib/delivery/refresh.ts +174 -0
- package/src/lib/delivery/repository-access.ts +97 -0
- package/src/lib/delivery/stages.ts +111 -0
- package/src/lib/delivery/token.ts +58 -0
- package/src/lib/frontmatter.ts +18 -0
- package/src/lib/opencode-plugin-package.ts +5 -5
- package/src/lib/openspec-cli.ts +116 -0
- package/src/lib/openspec-scopes.ts +86 -0
- package/src/lib/orchestrator/adapters/claude-args.ts +50 -0
- package/src/lib/orchestrator/adapters/claude.ts +48 -40
- package/src/lib/orchestrator/adapters/opencode.ts +2 -0
- package/src/lib/orchestrator/editor.ts +9 -9
- package/src/lib/orchestrator/openspec-store.ts +14 -54
- package/src/lib/orchestrator/projection-claude-commands.ts +36 -0
- package/src/lib/orchestrator/projection-claude-links.ts +159 -0
- package/src/lib/orchestrator/projection-claude-skills.ts +19 -137
- package/src/lib/orchestrator/projection-entries.ts +45 -14
- package/src/lib/orchestrator/projection-runtime-documents.ts +15 -6
- package/src/lib/orchestrator/projection-types.ts +3 -0
- package/src/lib/orchestrator/refresh-working-claude-links.ts +87 -0
- package/src/lib/orchestrator/session-prepare.ts +45 -5
- package/src/lib/orchestrator/setup-compatibilities.ts +18 -75
- package/src/lib/orchestrator/types.ts +2 -2
- package/src/lib/orchestrator/wrap-pass.ts +22 -1
- package/src/lib/package-paths.ts +8 -0
- package/src/opencode/companion-hooks.ts +64 -5
- package/src/opencode/companion-policy.ts +37 -18
- package/src/opencode/projected-guidance.ts +4 -1
- package/src/opencode/session-prepare.ts +2 -1
- package/src/opencode/tui.tsx +4 -2
- package/src/runtime/artifact-refusal.ts +63 -0
- package/src/runtime/claude-delivery-plan.ts +89 -7
- package/src/runtime/commit-attribution-guard.ts +181 -0
- package/src/runtime/companion-guidance.ts +15 -3
- package/src/runtime/companion-instructions.ts +340 -0
- package/src/runtime/error-message.ts +4 -0
- package/src/runtime/index.ts +5 -0
- package/src/runtime/managed-block.ts +117 -0
- package/src/runtime/markdown-scan.ts +266 -0
- package/src/runtime/policy.ts +15 -1
- package/src/runtime/projected-flag.ts +7 -0
- package/src/runtime/projected-guidance.ts +1 -0
- package/src/runtime/runtime-document-manifest.ts +62 -11
- package/src/runtime/secret-guard.ts +148 -0
- package/src/runtime/secret-patterns.ts +164 -0
- package/src/runtime/secret-scan.ts +90 -0
- package/src/runtime/session-kind.ts +13 -0
- package/src/runtime/shell-heredoc.ts +81 -0
- package/src/runtime/wrap-state.ts +36 -2
- package/src/runtime/write-atomic.ts +64 -0
- package/src/templates/capabilities/openspec-cap/mate-minimal/schema.yaml +8 -6
- package/src/templates/capabilities/openspec-cap/mate-minimal/templates/spec.md +3 -5
- package/src/templates/capabilities/openspec-cap/mate-v1/schema.yaml +7 -9
- package/src/templates/capabilities/openspec-cap/mate-v1/templates/spec.md +3 -5
- package/src/templates/capabilities/openspec-cap/openspec-conventions.yaml +10 -12
- package/src/templates/mate-skills/THIRD-PARTY-NOTICES.md +40 -0
- package/src/templates/mate-skills/agents/mate-apply-change/SKILL.md +76 -0
- package/src/templates/mate-skills/agents/mate-apply-change/references/openspec-apply-change.md +198 -0
- package/src/templates/mate-skills/agents/mate-archive-change/SKILL.md +59 -0
- package/src/templates/mate-skills/{claude/mate-openspec-backfill → agents/mate-backfill-spec}/SKILL.md +4 -4
- package/src/templates/mate-skills/agents/mate-clarify-intent/SKILL.md +95 -0
- package/src/templates/mate-skills/agents/mate-explore-change/SKILL.md +50 -0
- package/src/templates/mate-skills/agents/mate-explore-change/references/openspec-explore.md +348 -0
- package/src/templates/mate-skills/agents/mate-grill-me/SKILL.md +3 -3
- package/src/templates/mate-skills/agents/mate-interview-me/SKILL.md +5 -5
- package/src/templates/mate-skills/agents/mate-propose-change/SKILL.md +103 -0
- package/src/templates/mate-skills/agents/mate-propose-change/references/openspec-propose.md +167 -0
- package/src/templates/mate-skills/agents/mate-propose-change/references/openspec-update-change.md +101 -0
- package/src/templates/mate-skills/{claude/mate-artifact-publish → agents/mate-publish-artifact}/SKILL.md +91 -23
- package/src/templates/mate-skills/agents/{mate-artifact-publish → mate-publish-artifact}/references/openspec.md +4 -3
- package/src/templates/mate-skills/agents/mate-show-me/SKILL.md +74 -16
- package/src/templates/mate-skills/agents/mate-simplify-code/SKILL.md +74 -68
- package/src/templates/mate-skills/claude/mate-apply-change/SKILL.md +76 -0
- package/src/templates/mate-skills/claude/mate-apply-change/references/openspec-apply-change.md +198 -0
- package/src/templates/mate-skills/claude/mate-archive-change/SKILL.md +59 -0
- package/src/templates/mate-skills/{agents/mate-openspec-backfill → claude/mate-backfill-spec}/SKILL.md +4 -4
- package/src/templates/mate-skills/claude/mate-clarify-intent/SKILL.md +95 -0
- package/src/templates/mate-skills/claude/mate-explore-change/SKILL.md +50 -0
- package/src/templates/mate-skills/claude/mate-explore-change/references/openspec-explore.md +348 -0
- package/src/templates/mate-skills/claude/mate-grill-me/SKILL.md +3 -3
- package/src/templates/mate-skills/claude/mate-interview-me/SKILL.md +5 -5
- package/src/templates/mate-skills/claude/mate-propose-change/SKILL.md +103 -0
- package/src/templates/mate-skills/claude/mate-propose-change/references/openspec-propose.md +167 -0
- package/src/templates/mate-skills/claude/mate-propose-change/references/openspec-update-change.md +101 -0
- package/src/templates/mate-skills/{agents/mate-artifact-publish → claude/mate-publish-artifact}/SKILL.md +91 -23
- package/src/templates/mate-skills/claude/{mate-artifact-publish → mate-publish-artifact}/references/openspec.md +4 -3
- package/src/templates/mate-skills/claude/mate-show-me/SKILL.md +74 -16
- package/src/templates/mate-skills/claude/mate-simplify-code/SKILL.md +74 -68
- package/src/templates/mate-skills/openspec-references.ts +140 -0
- package/src/templates/root/TEMPLATE_AGENTS.md +2 -2
- package/src/tools/setup/__snapshots__/runtime-surface-golden.test.ts.snap +3727 -320
- package/src/tools/setup/capabilities/context-mode.ts +25 -5
- package/src/tools/setup/capabilities/graphify-gate.ts +31 -0
- package/src/tools/setup/capabilities/graphify-secrets.ts +255 -0
- package/src/tools/setup/capabilities/graphify.ts +12 -0
- package/src/tools/setup/capabilities/openspec.ts +151 -270
- package/src/tools/setup/capabilities/tokensave.ts +36 -3
- package/src/tools/setup/context-services.ts +9 -17
- package/src/tools/setup/dynamic-plugins/install.ts +24 -28
- package/src/tools/setup/engine.ts +97 -21
- package/src/tools/setup/managed-output.ts +51 -0
- package/src/tools/setup/mate.ts +86 -11
- package/src/tools/setup/plugin.ts +35 -0
- package/src/tools/setup/plugins/guidance.ts +7 -4
- package/src/tools/setup/providers/agent-file-sections.ts +4 -2
- package/src/tools/setup/providers/claude-attribution.ts +65 -0
- package/src/tools/setup/providers/claude-plugin-folders.ts +625 -0
- package/src/tools/setup/providers/claude-plugin-hooks.ts +39 -19
- package/src/tools/setup/providers/claude.ts +284 -8
- package/src/tools/setup/providers/opencode.ts +180 -1
- package/src/tools/setup/utils.ts +14 -4
- package/src/tools/setup.ts +126 -10
- package/wrappers/bin/graphify +52 -4
- package/wrappers/bin/openspec +33 -9
- package/wrappers/lib/graphify-gate.mjs +8 -0
- package/src/cli/commands/studio/views/workflow/transcript.tsx +0 -190
- package/src/tools/setup/runtime-documents.ts +0 -291
|
@@ -51,10 +51,16 @@ export interface ReportMetric {
|
|
|
51
51
|
detail?: string;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
export const REPORT_STATUS_TONES = ["ok", "warn", "error", "info", "neutral"] as const;
|
|
55
|
+
|
|
56
|
+
export type ReportStatusTone = (typeof REPORT_STATUS_TONES)[number];
|
|
57
|
+
|
|
54
58
|
export interface ReportStatus {
|
|
55
59
|
label: string;
|
|
56
60
|
status: string;
|
|
57
61
|
detail?: string;
|
|
62
|
+
/** Presentation tone; inferred from `status` when absent. */
|
|
63
|
+
tone?: ReportStatusTone;
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
export interface ReportSectionBase {
|
|
@@ -88,13 +94,21 @@ export interface ReportStatusesSection extends ReportSectionBase {
|
|
|
88
94
|
items: ReportStatus[];
|
|
89
95
|
}
|
|
90
96
|
|
|
97
|
+
export const REPORT_TEXT_FORMATS = ["plain", "markdown"] as const;
|
|
98
|
+
|
|
99
|
+
export type ReportTextFormat = (typeof REPORT_TEXT_FORMATS)[number];
|
|
100
|
+
|
|
91
101
|
export interface ReportTextSection extends ReportSectionBase {
|
|
92
102
|
type: "text";
|
|
93
103
|
content: string;
|
|
104
|
+
/** `plain` (default) keeps the text verbatim; `markdown` renders the safe subset. */
|
|
105
|
+
format?: ReportTextFormat;
|
|
94
106
|
}
|
|
95
107
|
|
|
96
108
|
export interface ReportDiagramSectionBase extends ReportSectionBase {
|
|
97
109
|
type: "diagram";
|
|
110
|
+
/** Markdown note rendered beneath the visual. */
|
|
111
|
+
caption?: string;
|
|
98
112
|
}
|
|
99
113
|
|
|
100
114
|
export interface ReportMermaidDiagramSection extends ReportDiagramSectionBase {
|
|
@@ -114,6 +128,8 @@ export interface ReportDiffSection extends ReportSectionBase {
|
|
|
114
128
|
type: "diff";
|
|
115
129
|
/** Unified diff as produced by `git diff`. */
|
|
116
130
|
patch: string;
|
|
131
|
+
/** Markdown note rendered beneath the visual. */
|
|
132
|
+
caption?: string;
|
|
117
133
|
}
|
|
118
134
|
|
|
119
135
|
export type ReportSection =
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from "../../lib/orchestrator/setup-preflight";
|
|
10
10
|
import {
|
|
11
11
|
getSetupSelectionsFromConfig,
|
|
12
|
-
|
|
12
|
+
keepCapabilitySettings,
|
|
13
13
|
} from "../../lib/orchestrator/setup-compatibilities";
|
|
14
14
|
import { confirm } from "../confirm";
|
|
15
15
|
import { selectCompanion } from "../companion-selector";
|
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
parseCapabilities,
|
|
20
20
|
parseGitMode,
|
|
21
21
|
parseFlags,
|
|
22
|
-
parseOpenSpecSchema,
|
|
23
22
|
parsePackageManagers,
|
|
24
23
|
} from "../parse-flags";
|
|
25
24
|
|
|
@@ -59,11 +58,10 @@ export async function runSetupCommand(argv: string[]): Promise<void> {
|
|
|
59
58
|
* via `setup.execute` and prints a summary table of allowed agents, package managers,
|
|
60
59
|
* and capabilities.
|
|
61
60
|
* @flags
|
|
62
|
-
* - `--
|
|
63
|
-
* - `--package-
|
|
64
|
-
* - `--
|
|
65
|
-
* - `--
|
|
66
|
-
* - `--git-mode <value>` — parsed via `parseGitMode`.
|
|
61
|
+
* - `--allowed-agent <agent>` — repeatable; parsed via `parseAllowedAgents`.
|
|
62
|
+
* - `--package-manager <name>` — repeatable; parsed via `parsePackageManagers`.
|
|
63
|
+
* - `--capability <name>` — repeatable; parsed via `parseCapabilities`.
|
|
64
|
+
* - `--git-mode <auto|default>` — parsed via `parseGitMode`.
|
|
67
65
|
* @remarks When any flag above is present, the whole selection is treated as
|
|
68
66
|
* explicit and the interactive picker is skipped.
|
|
69
67
|
*/
|
|
@@ -142,13 +140,11 @@ async function runSetupFlow(
|
|
|
142
140
|
const flagAllowedAgents = parseAllowedAgents(flags);
|
|
143
141
|
const flagPackageManagers = parsePackageManagers(flags);
|
|
144
142
|
const flagCapabilities = parseCapabilities(flags);
|
|
145
|
-
const flagOpenSpecSchema = parseOpenSpecSchema(flags);
|
|
146
143
|
const flagGitMode = parseGitMode(flags);
|
|
147
144
|
const hasExplicitSelections =
|
|
148
145
|
flagAllowedAgents !== undefined ||
|
|
149
146
|
flagPackageManagers !== undefined ||
|
|
150
147
|
flagCapabilities !== undefined ||
|
|
151
|
-
flagOpenSpecSchema !== undefined ||
|
|
152
148
|
flagGitMode !== undefined;
|
|
153
149
|
|
|
154
150
|
const currentSelections =
|
|
@@ -164,12 +160,8 @@ async function runSetupFlow(
|
|
|
164
160
|
? {
|
|
165
161
|
allowedAgents: flagAllowedAgents ?? currentSelections.allowedAgents,
|
|
166
162
|
packageManagers: flagPackageManagers ?? currentSelections.packageManagers,
|
|
167
|
-
capabilities:
|
|
168
|
-
flagCapabilities ?? currentSelections.capabilities,
|
|
169
|
-
flagOpenSpecSchema,
|
|
170
|
-
),
|
|
163
|
+
capabilities: flagCapabilities ?? currentSelections.capabilities,
|
|
171
164
|
git: flagGitMode ?? currentSelections.selectedGitMode,
|
|
172
|
-
selectedOpenSpecSchema: flagOpenSpecSchema ?? currentSelections.selectedOpenSpecSchema,
|
|
173
165
|
selectedGitMode: flagGitMode ?? currentSelections.selectedGitMode,
|
|
174
166
|
}
|
|
175
167
|
: await selectCompatibilities({ initialSelections: currentSelections });
|
|
@@ -183,7 +175,7 @@ async function runSetupFlow(
|
|
|
183
175
|
{
|
|
184
176
|
allowedAgents: selections.allowedAgents,
|
|
185
177
|
packageManagers: selections.packageManagers,
|
|
186
|
-
capabilities: selections.capabilities,
|
|
178
|
+
capabilities: keepCapabilitySettings(selections.capabilities, currentSelections.capabilities),
|
|
187
179
|
git: selections.selectedGitMode,
|
|
188
180
|
},
|
|
189
181
|
{ cwd: targetCwd },
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Where Studio serves its own client script: one fixed name, never mapped to a path. */
|
|
2
|
+
export const STUDIO_CLIENT_ROUTE = "/studio/client.js";
|
|
3
|
+
|
|
4
|
+
/** `null` for any name but the fixed one, so a request never maps to a file. */
|
|
5
|
+
export async function studioClientAsset(pathname: string): Promise<Response | null> {
|
|
6
|
+
if (pathname !== STUDIO_CLIENT_ROUTE) return null;
|
|
7
|
+
// Loaded on request, so the CLI's startup never reads the built script.
|
|
8
|
+
const { STUDIO_CLIENT_BUNDLE } = await import("./views/studio-client.generated");
|
|
9
|
+
return new Response(STUDIO_CLIENT_BUNDLE, {
|
|
10
|
+
headers: {
|
|
11
|
+
"content-type": "text/javascript; charset=utf-8",
|
|
12
|
+
"cache-control": "no-cache",
|
|
13
|
+
"x-content-type-options": "nosniff",
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
}
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
|
|
4
|
-
import { getWrapperBinPath } from "../../../lib/package-paths";
|
|
1
|
+
import { runOpenSpec, type OpenSpecRun, type RunOpenSpec } from "../../../lib/openspec-cli";
|
|
5
2
|
|
|
6
3
|
export interface OpenSpecFailure {
|
|
7
4
|
command: string;
|
|
@@ -10,60 +7,16 @@ export interface OpenSpecFailure {
|
|
|
10
7
|
|
|
11
8
|
export type OpenSpecResult<T> = { ok: true; value: T } | { ok: false; failure: OpenSpecFailure };
|
|
12
9
|
|
|
13
|
-
export interface OpenSpecRunOutput {
|
|
14
|
-
stdout: string;
|
|
15
|
-
stderr: string;
|
|
16
|
-
code: number | null;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
10
|
export interface OpenSpecCliDeps {
|
|
20
|
-
|
|
21
|
-
run?: (bin: string, args: string[], companionPath: string) => Promise<OpenSpecRunOutput>;
|
|
11
|
+
run?: RunOpenSpec;
|
|
22
12
|
}
|
|
23
13
|
|
|
24
14
|
/** The Capability whose CLI produces the workflow a companion's changes move through. */
|
|
25
15
|
export const WORKFLOW_CAPABILITY_ID = "openspec";
|
|
26
16
|
|
|
27
|
-
/** Upper bound on one collection call; a hung
|
|
17
|
+
/** Upper bound on one collection call; a hung CLI must not hold a request open. */
|
|
28
18
|
const OPENSPEC_TIMEOUT_MS = 60_000;
|
|
29
19
|
|
|
30
|
-
function openSpecWrapperPath(): string {
|
|
31
|
-
return path.join(getWrapperBinPath(), "openspec");
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* The wrapper resolves its OpenSpec root from `MATE_ARTIFACT_PATH`, so the
|
|
36
|
-
* companion is selected by environment rather than by cwd — the studio server
|
|
37
|
-
* has no Repository Link to walk up from.
|
|
38
|
-
*/
|
|
39
|
-
function runOpenSpecWrapper(
|
|
40
|
-
bin: string,
|
|
41
|
-
args: string[],
|
|
42
|
-
companionPath: string,
|
|
43
|
-
): Promise<OpenSpecRunOutput> {
|
|
44
|
-
return new Promise((resolve, reject) => {
|
|
45
|
-
const child = spawn(bin, args, {
|
|
46
|
-
cwd: companionPath,
|
|
47
|
-
env: { ...process.env, MATE_ARTIFACT_PATH: companionPath },
|
|
48
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
49
|
-
timeout: OPENSPEC_TIMEOUT_MS,
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
let stdout = "";
|
|
53
|
-
let stderr = "";
|
|
54
|
-
child.stdout?.setEncoding("utf8");
|
|
55
|
-
child.stderr?.setEncoding("utf8");
|
|
56
|
-
child.stdout?.on("data", (chunk: string) => {
|
|
57
|
-
stdout += chunk;
|
|
58
|
-
});
|
|
59
|
-
child.stderr?.on("data", (chunk: string) => {
|
|
60
|
-
stderr += chunk;
|
|
61
|
-
});
|
|
62
|
-
child.once("error", reject);
|
|
63
|
-
child.once("close", (code) => resolve({ stdout, stderr, code }));
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
20
|
function firstJsonValue(stdout: string): unknown {
|
|
68
21
|
const start = stdout.search(/[[{]/);
|
|
69
22
|
if (start < 0) return undefined;
|
|
@@ -74,29 +27,28 @@ function firstJsonValue(stdout: string): unknown {
|
|
|
74
27
|
}
|
|
75
28
|
}
|
|
76
29
|
|
|
77
|
-
function reasonFor(output:
|
|
30
|
+
function reasonFor(output: OpenSpecRun): string {
|
|
78
31
|
const detail = (output.stderr.trim() || output.stdout.trim()).split("\n").slice(-3).join("; ");
|
|
79
32
|
const status = `exit code ${output.code ?? "unknown"}`;
|
|
80
33
|
return detail ? `${status}: ${detail}` : status;
|
|
81
34
|
}
|
|
82
35
|
|
|
83
36
|
/**
|
|
84
|
-
* Reads one OpenSpec JSON command
|
|
85
|
-
* every failure — spawn, exit status, unparseable output — becomes a
|
|
86
|
-
* failure so one unreadable companion cannot take the server down.
|
|
37
|
+
* Reads one OpenSpec JSON command, run in the Companion Repository. Never
|
|
38
|
+
* throws: every failure — spawn, exit status, unparseable output — becomes a
|
|
39
|
+
* typed failure so one unreadable companion cannot take the server down.
|
|
87
40
|
*/
|
|
88
41
|
export async function readOpenSpecJson<T>(
|
|
89
42
|
companionPath: string,
|
|
90
43
|
args: string[],
|
|
91
44
|
deps: OpenSpecCliDeps = {},
|
|
92
45
|
): Promise<OpenSpecResult<T>> {
|
|
93
|
-
const bin = (deps.wrapperPath ?? openSpecWrapperPath)();
|
|
94
46
|
const command = `openspec ${args.join(" ")}`;
|
|
95
|
-
const run = deps.run ??
|
|
47
|
+
const run = deps.run ?? runOpenSpec;
|
|
96
48
|
|
|
97
|
-
let output:
|
|
49
|
+
let output: OpenSpecRun;
|
|
98
50
|
try {
|
|
99
|
-
output = await run(
|
|
51
|
+
output = await run(args, { cwd: companionPath, timeout: OPENSPEC_TIMEOUT_MS });
|
|
100
52
|
} catch (error) {
|
|
101
53
|
return {
|
|
102
54
|
ok: false,
|
|
@@ -5,6 +5,8 @@ export const STUDIO_HOSTNAME = "127.0.0.1";
|
|
|
5
5
|
export const VAULT_DIR_ROUTE = "/api/vault/dir";
|
|
6
6
|
/** Name-filter matches over the whole listed tree, as an HTML fragment. */
|
|
7
7
|
export const VAULT_FILTER_ROUTE = "/api/vault/filter";
|
|
8
|
+
/** The open file's breadcrumb and pane as an HTML fragment, for opening a file in place. */
|
|
9
|
+
export const VAULT_PANE_ROUTE = "/api/vault/pane";
|
|
8
10
|
/** Server-sent listing generations: a newer one marks the displayed tree stale. */
|
|
9
11
|
export const VAULT_CHANGES_ROUTE = "/api/vault/changes";
|
|
10
12
|
|
|
@@ -18,6 +18,7 @@ export const VIEW_PARAM = "view";
|
|
|
18
18
|
export const REFRESH_PARAM = "refresh";
|
|
19
19
|
export const FILE_PARAM = "path";
|
|
20
20
|
export const DIR_PARAM = "dir";
|
|
21
|
+
export const MODE_PARAM = "mode";
|
|
21
22
|
|
|
22
23
|
/** Every URL parameter Studio names; no other module writes one out. */
|
|
23
24
|
export const STUDIO_PARAMS: readonly string[] = [
|
|
@@ -26,6 +27,7 @@ export const STUDIO_PARAMS: readonly string[] = [
|
|
|
26
27
|
REFRESH_PARAM,
|
|
27
28
|
FILE_PARAM,
|
|
28
29
|
DIR_PARAM,
|
|
30
|
+
MODE_PARAM,
|
|
29
31
|
];
|
|
30
32
|
|
|
31
33
|
/** Filled into a prompt when no change is named, so a prompt is never half-written. */
|
|
@@ -42,9 +44,14 @@ interface SelectionBase {
|
|
|
42
44
|
refresh: boolean;
|
|
43
45
|
}
|
|
44
46
|
|
|
47
|
+
/** An open file is read rendered or edited raw; read is every other value. */
|
|
48
|
+
export type VaultMode = "read" | "edit";
|
|
49
|
+
|
|
45
50
|
export interface StudioVaultSelection extends SelectionBase {
|
|
46
51
|
view: "vault";
|
|
47
52
|
openPath: string | null;
|
|
53
|
+
/** Only meaningful with an open file, and only where the write path is reached. */
|
|
54
|
+
mode: VaultMode;
|
|
48
55
|
/** A folder of the listed tree; never looked up on disk. */
|
|
49
56
|
openDir: string | null;
|
|
50
57
|
}
|
|
@@ -109,6 +116,7 @@ function readVault(params: URLSearchParams): StudioVaultSelection {
|
|
|
109
116
|
...readBase(params),
|
|
110
117
|
view: "vault",
|
|
111
118
|
openPath: readText(params.get(FILE_PARAM)),
|
|
119
|
+
mode: params.get(MODE_PARAM) === "edit" ? "edit" : "read",
|
|
112
120
|
openDir: readText(params.get(DIR_PARAM)),
|
|
113
121
|
};
|
|
114
122
|
}
|
|
@@ -128,7 +136,7 @@ export function parseVaultSelection(url: URL): StudioVaultSelection {
|
|
|
128
136
|
/** Drops the vault's target. */
|
|
129
137
|
export function switchView(selection: StudioSelection, view: StudioView): StudioSelection {
|
|
130
138
|
const base = { companionDigest: selection.companionDigest, refresh: false };
|
|
131
|
-
if (view === "vault") return { ...base, view, openPath: null, openDir: null };
|
|
139
|
+
if (view === "vault") return { ...base, view, openPath: null, mode: "read", openDir: null };
|
|
132
140
|
return { ...base, view };
|
|
133
141
|
}
|
|
134
142
|
|
|
@@ -146,6 +154,7 @@ export function openFile(selection: StudioSelection, path: string): StudioVaultS
|
|
|
146
154
|
refresh: false,
|
|
147
155
|
view: "vault",
|
|
148
156
|
openPath: path,
|
|
157
|
+
mode: "read",
|
|
149
158
|
openDir: null,
|
|
150
159
|
};
|
|
151
160
|
}
|
|
@@ -157,10 +166,16 @@ export function openFolder(selection: StudioSelection, dir: string | null): Stud
|
|
|
157
166
|
refresh: false,
|
|
158
167
|
view: "vault",
|
|
159
168
|
openPath: null,
|
|
169
|
+
mode: "read",
|
|
160
170
|
openDir: dir,
|
|
161
171
|
};
|
|
162
172
|
}
|
|
163
173
|
|
|
174
|
+
/** The open file in the other mode; a selection with no open file is returned as it is. */
|
|
175
|
+
export function withMode(selection: StudioVaultSelection, mode: VaultMode): StudioVaultSelection {
|
|
176
|
+
return selection.openPath ? { ...selection, mode, refresh: false } : selection;
|
|
177
|
+
}
|
|
178
|
+
|
|
164
179
|
export function refresh(selection: StudioSelection): StudioSelection {
|
|
165
180
|
return { ...selection, refresh: true };
|
|
166
181
|
}
|
|
@@ -174,6 +189,9 @@ export function toFields(selection: StudioSelection): SelectionField[] {
|
|
|
174
189
|
if (selection.view !== "dashboard") fields.push({ name: VIEW_PARAM, value: selection.view });
|
|
175
190
|
if (selection.view === "vault") {
|
|
176
191
|
if (selection.openPath) fields.push({ name: FILE_PARAM, value: selection.openPath });
|
|
192
|
+
if (selection.openPath && selection.mode === "edit") {
|
|
193
|
+
fields.push({ name: MODE_PARAM, value: "edit" });
|
|
194
|
+
}
|
|
177
195
|
if (selection.openDir) fields.push({ name: DIR_PARAM, value: selection.openDir });
|
|
178
196
|
}
|
|
179
197
|
if (selection.refresh) fields.push({ name: REFRESH_PARAM, value: "1" });
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
VAULT_CHANGES_ROUTE,
|
|
19
19
|
VAULT_DIR_ROUTE,
|
|
20
20
|
VAULT_FILTER_ROUTE,
|
|
21
|
+
VAULT_PANE_ROUTE,
|
|
21
22
|
} from "./routes";
|
|
22
23
|
import {
|
|
23
24
|
COMPANION_DIGEST_PATTERN,
|
|
@@ -37,6 +38,7 @@ import {
|
|
|
37
38
|
type TerminalConnection,
|
|
38
39
|
type TerminalRegistryOptions,
|
|
39
40
|
} from "./terminal";
|
|
41
|
+
import { studioClientAsset } from "./client-asset";
|
|
40
42
|
import { terminalAsset } from "./terminal-assets";
|
|
41
43
|
import {
|
|
42
44
|
createLaunchResolver,
|
|
@@ -143,6 +145,11 @@ async function renderVaultView(page: StudioPage): Promise<string> {
|
|
|
143
145
|
return views.renderVaultView(page);
|
|
144
146
|
}
|
|
145
147
|
|
|
148
|
+
async function renderVaultPane(page: StudioPage): Promise<string> {
|
|
149
|
+
const views = await import("./views/document");
|
|
150
|
+
return views.renderVaultPane(page);
|
|
151
|
+
}
|
|
152
|
+
|
|
146
153
|
async function renderVaultChildren(page: StudioPage): Promise<string | null> {
|
|
147
154
|
const views = await import("./views/document");
|
|
148
155
|
return views.renderVaultChildren(page);
|
|
@@ -158,12 +165,17 @@ async function collectVaultState(
|
|
|
158
165
|
vault: VaultManager,
|
|
159
166
|
companionPath: string,
|
|
160
167
|
selection: StudioVaultSelection,
|
|
161
|
-
options: { tree: "prefetch" | "await" },
|
|
168
|
+
options: { tree: "prefetch" | "await" | "skip" },
|
|
162
169
|
): Promise<StudioVaultPage> {
|
|
163
170
|
const { vaultState } = await import("./views/vault/state");
|
|
164
171
|
return vaultState(vault, companionPath, selection, options);
|
|
165
172
|
}
|
|
166
173
|
|
|
174
|
+
/** A pane is never a refresh, so the listing it skips is not asked to be rebuilt. */
|
|
175
|
+
function plainSelection(selection: StudioVaultSelection): StudioVaultSelection {
|
|
176
|
+
return { ...selection, refresh: false };
|
|
177
|
+
}
|
|
178
|
+
|
|
167
179
|
function html(body: string): Response {
|
|
168
180
|
return new Response(body, {
|
|
169
181
|
headers: { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" },
|
|
@@ -435,6 +447,9 @@ export function createStudioFetch(
|
|
|
435
447
|
);
|
|
436
448
|
}
|
|
437
449
|
|
|
450
|
+
const clientAsset = await studioClientAsset(url.pathname);
|
|
451
|
+
if (clientAsset) return respond(clientAsset);
|
|
452
|
+
|
|
438
453
|
if (url.pathname.startsWith("/studio/terminal/") && terminal) {
|
|
439
454
|
const asset = await terminalAsset(url.pathname);
|
|
440
455
|
if (asset) return respond(asset);
|
|
@@ -527,6 +542,32 @@ export function createStudioFetch(
|
|
|
527
542
|
);
|
|
528
543
|
}
|
|
529
544
|
|
|
545
|
+
if (url.pathname === VAULT_PANE_ROUTE) {
|
|
546
|
+
const collected = await inventory();
|
|
547
|
+
const selection = parseVaultSelection(url);
|
|
548
|
+
const companion = resolveCompanion(collected, selection.companionDigest);
|
|
549
|
+
if (!companion) return respond(json({ reason: "no registered companion was selected" }, 400));
|
|
550
|
+
const state = await collectVaultState(vault, companion.path, plainSelection(selection), {
|
|
551
|
+
tree: "skip",
|
|
552
|
+
});
|
|
553
|
+
return respond(
|
|
554
|
+
new Response(
|
|
555
|
+
await renderVaultPane({
|
|
556
|
+
inventory: collected,
|
|
557
|
+
selection,
|
|
558
|
+
companion,
|
|
559
|
+
payload: null,
|
|
560
|
+
error: null,
|
|
561
|
+
collectedAt: null,
|
|
562
|
+
writable,
|
|
563
|
+
vault: state,
|
|
564
|
+
terminal: null,
|
|
565
|
+
}),
|
|
566
|
+
{ headers: { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" } },
|
|
567
|
+
),
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
|
|
530
571
|
if (url.pathname === VAULT_DIR_ROUTE || url.pathname === VAULT_FILTER_ROUTE) {
|
|
531
572
|
const selection = parseVaultSelection(url);
|
|
532
573
|
const selected = await selectedCompanion(selection.companionDigest, inventory);
|