@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
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Thin shim for src/hooks/companion-instructions.ts, loaded through ts-loader.mjs: the
|
|
4
|
+
* installed plugin sits inside node_modules, where node's native type stripping is off.
|
|
5
|
+
*/
|
|
6
|
+
import "./ts-loader.mjs";
|
|
7
|
+
|
|
8
|
+
const { run } = await import("../../src/hooks/companion-instructions.ts");
|
|
9
|
+
process.exitCode = await run();
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"description": "Mate hooks \u2014 PreToolUse artifact-path guardrail, SessionStart banner, companion guidance
|
|
2
|
+
"description": "Mate hooks \u2014 PreToolUse artifact-path and secret guardrail, SessionStart banner, companion guidance, session prepare then companion instructions, SubagentStart companion instructions",
|
|
3
3
|
"hooks": {
|
|
4
4
|
"PreToolUse": [
|
|
5
5
|
{
|
|
6
|
-
"matcher": "Write|Edit|MultiEdit|Bash",
|
|
6
|
+
"matcher": "Read|Grep|Write|Edit|MultiEdit|NotebookEdit|Bash",
|
|
7
7
|
"hooks": [
|
|
8
8
|
{
|
|
9
9
|
"type": "command",
|
|
@@ -38,6 +38,16 @@
|
|
|
38
38
|
}
|
|
39
39
|
]
|
|
40
40
|
}
|
|
41
|
+
],
|
|
42
|
+
"SubagentStart": [
|
|
43
|
+
{
|
|
44
|
+
"hooks": [
|
|
45
|
+
{
|
|
46
|
+
"type": "command",
|
|
47
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/companion-instructions.mjs\""
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
41
51
|
]
|
|
42
52
|
}
|
|
43
53
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniqbit/mate-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-canary.0",
|
|
4
4
|
"description": "Core framework and plugin APIs for Mate.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"./runtime": "./src/runtime/index.ts",
|
|
25
25
|
"./opencode": "./src/opencode/index.ts",
|
|
26
26
|
"./opencode/tui": "./src/opencode/tui.tsx",
|
|
27
|
-
"./tui": "./src/tui.ts"
|
|
27
|
+
"./tui": "./src/tui.ts",
|
|
28
|
+
"./brand": "./src/brand/index.ts"
|
|
28
29
|
},
|
|
29
30
|
"publishConfig": {
|
|
30
31
|
"access": "public",
|
|
@@ -32,6 +33,8 @@
|
|
|
32
33
|
},
|
|
33
34
|
"scripts": {
|
|
34
35
|
"test": "CI=1 bun ../../test/run-isolated.ts",
|
|
36
|
+
"test:unit": "CI=1 bun ../../test/run-isolated.ts --suite=unit",
|
|
37
|
+
"test:integration": "CI=1 bun ../../test/run-isolated.ts --suite=integration",
|
|
35
38
|
"typecheck": "bunx tsc --noEmit -p ../../tsconfig.json",
|
|
36
39
|
"lint": "oxlint src/ opencode-plugin/",
|
|
37
40
|
"lint:fix": "oxlint src/ opencode-plugin/ --fix",
|
|
@@ -39,10 +42,14 @@
|
|
|
39
42
|
"format:check": "oxfmt src/ opencode-plugin/ \"!**/*.generated.*\" --check"
|
|
40
43
|
},
|
|
41
44
|
"dependencies": {
|
|
45
|
+
"@fission-ai/openspec": "1.15.0",
|
|
42
46
|
"@opencode/plugin": "^2.0.23",
|
|
43
47
|
"@opentui/core": "^0.5.14",
|
|
44
48
|
"@opentui/keymap": "^0.5.14",
|
|
45
49
|
"@opentui/solid": "^0.5.14",
|
|
50
|
+
"@secretlint/core": "13.0.7",
|
|
51
|
+
"@secretlint/secretlint-rule-preset-recommend": "13.0.7",
|
|
52
|
+
"@secretlint/types": "13.0.7",
|
|
46
53
|
"@tanstack/highlight": "0.0.10",
|
|
47
54
|
"@xterm/addon-clipboard": "^0.2.0",
|
|
48
55
|
"@xterm/addon-fit": "^0.11.0",
|
|
@@ -51,6 +58,7 @@
|
|
|
51
58
|
"@xterm/xterm": "^6.0.0",
|
|
52
59
|
"hono": "^4.13.5",
|
|
53
60
|
"ink": "^7.1.0",
|
|
61
|
+
"marked": "^18.1.1",
|
|
54
62
|
"react": "^19.2.7",
|
|
55
63
|
"react-doctor": "0.9.13",
|
|
56
64
|
"semver": "^7.8.5",
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Generated by scripts/brand/generate.ts from assets/logo-kit. Do not edit.
|
|
2
|
+
import type { LogoArt, LogoForm } from "./types";
|
|
3
|
+
|
|
4
|
+
export const LOGO_ART: Record<LogoForm, LogoArt> = {
|
|
5
|
+
lockup: {
|
|
6
|
+
block: [
|
|
7
|
+
[{"kind":"pane","text":"▄███████"},{"kind":"space","text":" "},{"kind":"note","text":"████▄"}],
|
|
8
|
+
[{"kind":"pane","text":"████████"},{"kind":"space","text":" "},{"kind":"note","text":"█████"},{"kind":"space","text":" "},{"kind":"word","text":"▄▄"}],
|
|
9
|
+
[{"kind":"pane","text":"████████"},{"kind":"space","text":" "},{"kind":"note","text":"▄▄▄▄▄"},{"kind":"space","text":" "},{"kind":"word","text":"██"}],
|
|
10
|
+
[{"kind":"pane","text":"████████"},{"kind":"space","text":" "},{"kind":"note","text":"█████"},{"kind":"space","text":" "},{"kind":"word","text":"▄█▀▀██▀▀█▄"},{"kind":"space","text":" "},{"kind":"word","text":"▄█▀▀██"},{"kind":"space","text":" "},{"kind":"word","text":"▀██▀▀"},{"kind":"space","text":" "},{"kind":"word","text":"▄█▀▀█▄"}],
|
|
11
|
+
[{"kind":"pane","text":"████████"},{"kind":"space","text":" "},{"kind":"note","text":"▀▀▀▀▀"},{"kind":"space","text":" "},{"kind":"word","text":"██"},{"kind":"space","text":" "},{"kind":"word","text":"██"},{"kind":"space","text":" "},{"kind":"word","text":"██"},{"kind":"space","text":" "},{"kind":"word","text":"██"},{"kind":"space","text":" "},{"kind":"word","text":"██"},{"kind":"space","text":" "},{"kind":"word","text":"██"},{"kind":"space","text":" "},{"kind":"word","text":"██▄▄██"}],
|
|
12
|
+
[{"kind":"pane","text":"████████"},{"kind":"space","text":" "},{"kind":"note","text":"█████"},{"kind":"space","text":" "},{"kind":"word","text":"██"},{"kind":"space","text":" "},{"kind":"word","text":"██"},{"kind":"space","text":" "},{"kind":"word","text":"██"},{"kind":"space","text":" "},{"kind":"word","text":"██"},{"kind":"space","text":" "},{"kind":"word","text":"██"},{"kind":"space","text":" "},{"kind":"word","text":"██"},{"kind":"space","text":" "},{"kind":"word","text":"██"}],
|
|
13
|
+
[{"kind":"pane","text":"▀███████"},{"kind":"space","text":" "},{"kind":"note","text":"████▀"},{"kind":"space","text":" "},{"kind":"word","text":"██"},{"kind":"space","text":" "},{"kind":"word","text":"██"},{"kind":"space","text":" "},{"kind":"word","text":"██"},{"kind":"space","text":" "},{"kind":"word","text":"▀█▄▄██▄"},{"kind":"space","text":" "},{"kind":"word","text":"▀█▄▄"},{"kind":"space","text":" "},{"kind":"word","text":"▀█▄▄▄▄"}],
|
|
14
|
+
],
|
|
15
|
+
ascii: [
|
|
16
|
+
" ####### ####",
|
|
17
|
+
"######## ##### ##",
|
|
18
|
+
"######## ##",
|
|
19
|
+
"######## ##### ######## ##### ##### #####",
|
|
20
|
+
"######## ##### ## ## ## ## ## ## ## ##",
|
|
21
|
+
"######## ## ## ## ## ## ## #######",
|
|
22
|
+
"######## ##### ## ## ## ## ## ## ##",
|
|
23
|
+
" ####### #### ## ## ## ###### ### ######",
|
|
24
|
+
],
|
|
25
|
+
columns: 52,
|
|
26
|
+
},
|
|
27
|
+
symbol: {
|
|
28
|
+
block: [
|
|
29
|
+
[{"kind":"pane","text":"▄███████"},{"kind":"space","text":" "},{"kind":"note","text":"████▄"}],
|
|
30
|
+
[{"kind":"pane","text":"████████"},{"kind":"space","text":" "},{"kind":"note","text":"█████"}],
|
|
31
|
+
[{"kind":"pane","text":"████████"},{"kind":"space","text":" "},{"kind":"note","text":"▄▄▄▄▄"}],
|
|
32
|
+
[{"kind":"pane","text":"████████"},{"kind":"space","text":" "},{"kind":"note","text":"█████"}],
|
|
33
|
+
[{"kind":"pane","text":"████████"},{"kind":"space","text":" "},{"kind":"note","text":"▀▀▀▀▀"}],
|
|
34
|
+
[{"kind":"pane","text":"████████"},{"kind":"space","text":" "},{"kind":"note","text":"█████"}],
|
|
35
|
+
[{"kind":"pane","text":"▀███████"},{"kind":"space","text":" "},{"kind":"note","text":"████▀"}],
|
|
36
|
+
],
|
|
37
|
+
ascii: [
|
|
38
|
+
" ####### ####",
|
|
39
|
+
"######## #####",
|
|
40
|
+
"########",
|
|
41
|
+
"######## #####",
|
|
42
|
+
"######## #####",
|
|
43
|
+
"########",
|
|
44
|
+
"######## #####",
|
|
45
|
+
" ####### ####",
|
|
46
|
+
],
|
|
47
|
+
columns: 14,
|
|
48
|
+
},
|
|
49
|
+
wordmark: {
|
|
50
|
+
block: [
|
|
51
|
+
[{"kind":"space","text":" "},{"kind":"word","text":"▄▄"}],
|
|
52
|
+
[{"kind":"space","text":" "},{"kind":"word","text":"██"}],
|
|
53
|
+
[{"kind":"word","text":"▄█▀▀██▀▀█▄"},{"kind":"space","text":" "},{"kind":"word","text":"▄█▀▀██"},{"kind":"space","text":" "},{"kind":"word","text":"▀██▀▀"},{"kind":"space","text":" "},{"kind":"word","text":"▄█▀▀█▄"}],
|
|
54
|
+
[{"kind":"word","text":"██"},{"kind":"space","text":" "},{"kind":"word","text":"██"},{"kind":"space","text":" "},{"kind":"word","text":"██"},{"kind":"space","text":" "},{"kind":"word","text":"██"},{"kind":"space","text":" "},{"kind":"word","text":"██"},{"kind":"space","text":" "},{"kind":"word","text":"██"},{"kind":"space","text":" "},{"kind":"word","text":"██▄▄██"}],
|
|
55
|
+
[{"kind":"word","text":"██"},{"kind":"space","text":" "},{"kind":"word","text":"██"},{"kind":"space","text":" "},{"kind":"word","text":"██"},{"kind":"space","text":" "},{"kind":"word","text":"██"},{"kind":"space","text":" "},{"kind":"word","text":"██"},{"kind":"space","text":" "},{"kind":"word","text":"██"},{"kind":"space","text":" "},{"kind":"word","text":"██"}],
|
|
56
|
+
[{"kind":"word","text":"██"},{"kind":"space","text":" "},{"kind":"word","text":"██"},{"kind":"space","text":" "},{"kind":"word","text":"██"},{"kind":"space","text":" "},{"kind":"word","text":"▀█▄▄██▄"},{"kind":"space","text":" "},{"kind":"word","text":"▀█▄▄"},{"kind":"space","text":" "},{"kind":"word","text":"▀█▄▄▄▄"}],
|
|
57
|
+
],
|
|
58
|
+
ascii: [
|
|
59
|
+
" ##",
|
|
60
|
+
" ##",
|
|
61
|
+
" ######## ##### ##### #####",
|
|
62
|
+
"## ## ## ## ## ## ## ##",
|
|
63
|
+
"## ## ## ## ## ## #######",
|
|
64
|
+
"## ## ## ## ## ## ##",
|
|
65
|
+
"## ## ## ###### ### ######",
|
|
66
|
+
],
|
|
67
|
+
columns: 34,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { LOGO_ART } from "./art.generated";
|
|
2
|
+
export { BRAND_ACCENT, BRAND_COLORS, type BrandColor, type BrandColorName } from "./palette";
|
|
3
|
+
export { BRAND_SVGS } from "./svg.generated";
|
|
4
|
+
export {
|
|
5
|
+
LOGO_WORD,
|
|
6
|
+
chooseLogo,
|
|
7
|
+
colorEnabled,
|
|
8
|
+
renderLogo,
|
|
9
|
+
type LogoChoice,
|
|
10
|
+
type LogoOptions,
|
|
11
|
+
} from "./terminal";
|
|
12
|
+
export type { BrandSvgs, LogoArt, LogoForm, LogoRow, LogoSegment, LogoSegmentKind } from "./types";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** A brand colour as a 24-bit hex value plus its nearest xterm 256-colour index. */
|
|
2
|
+
export interface BrandColor {
|
|
3
|
+
hex: string;
|
|
4
|
+
ansi256: number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/** The five colours of the Mate identity, as defined in `assets/logo-kit/BRAND.md`. */
|
|
8
|
+
export const BRAND_COLORS = {
|
|
9
|
+
ink: { hex: "#1F2421", ansi256: 235 },
|
|
10
|
+
mateGreen: { hex: "#2E7D5B", ansi256: 29 },
|
|
11
|
+
cream: { hex: "#F4F0E8", ansi256: 255 },
|
|
12
|
+
lightGreen: { hex: "#7FD0A6", ansi256: 115 },
|
|
13
|
+
night: { hex: "#15191A", ansi256: 234 },
|
|
14
|
+
} as const satisfies Record<string, BrandColor>;
|
|
15
|
+
|
|
16
|
+
export type BrandColorName = keyof typeof BRAND_COLORS;
|
|
17
|
+
|
|
18
|
+
/** The accent used on surfaces whose background is unknown; it reads on both light and dark. */
|
|
19
|
+
export const BRAND_ACCENT = BRAND_COLORS.mateGreen.hex;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Generated by scripts/brand/generate.ts from assets/logo-kit. Do not edit.
|
|
2
|
+
import type { BrandSvgs } from "./types";
|
|
3
|
+
|
|
4
|
+
export const BRAND_SVGS: BrandSvgs = {
|
|
5
|
+
symbolColor: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 256\" width=\"256\" height=\"256\" role=\"img\" aria-labelledby=\"title\"><title id=\"title\">mate logo</title><g id=\"symbol\"><path fill=\"#1F2421\" d=\"M36 16H144V240H36A20 20 0 0 1 16 220V36A20 20 0 0 1 36 16Z\"/><path fill=\"#2E7D5B\" d=\"M160 16H220A20 20 0 0 1 240 36V80H160Z M160 96H240V160H160Z M160 176H240V220A20 20 0 0 1 220 240H160Z\"/></g></svg>",
|
|
6
|
+
symbolOnDark: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 256\" width=\"256\" height=\"256\" role=\"img\" aria-labelledby=\"title\"><title id=\"title\">mate logo</title><g id=\"symbol\"><path fill=\"#F4F0E8\" d=\"M36 16H144V240H36A20 20 0 0 1 16 220V36A20 20 0 0 1 36 16Z\"/><path fill=\"#7FD0A6\" d=\"M160 16H220A20 20 0 0 1 240 36V80H160Z M160 96H240V160H160Z M160 176H240V220A20 20 0 0 1 220 240H160Z\"/></g></svg>",
|
|
7
|
+
favicon: "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 256 256\" width=\"256\" height=\"256\" role=\"img\" aria-labelledby=\"title\"><title id=\"title\">mate logo</title><rect width=\"256\" height=\"256\" rx=\"48\" fill=\"#1F2421\"/><g id=\"symbol\" transform=\"translate(32 32) scale(0.75)\"><path fill=\"#F4F0E8\" d=\"M36 16H144V240H36A20 20 0 0 1 16 220V36A20 20 0 0 1 36 16Z\"/><path fill=\"#7FD0A6\" d=\"M160 16H220A20 20 0 0 1 240 36V80H160Z M160 96H240V160H160Z M160 176H240V220A20 20 0 0 1 220 240H160Z\"/></g></svg>",
|
|
8
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { LOGO_ART } from "./art.generated";
|
|
2
|
+
import { BRAND_COLORS } from "./palette";
|
|
3
|
+
import type { LogoForm, LogoRow } from "./types";
|
|
4
|
+
|
|
5
|
+
export interface LogoOptions {
|
|
6
|
+
/** The largest form wanted; it falls back to smaller forms when it does not fit. */
|
|
7
|
+
form: LogoForm;
|
|
8
|
+
/** Columns available on the target stream. */
|
|
9
|
+
columns: number;
|
|
10
|
+
isTTY: boolean;
|
|
11
|
+
env?: Record<string, string | undefined>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type LogoChoice =
|
|
15
|
+
| { kind: "block"; form: LogoForm; rows: readonly LogoRow[]; color: boolean }
|
|
16
|
+
| { kind: "ascii"; form: LogoForm; rows: readonly string[] }
|
|
17
|
+
| { kind: "word" };
|
|
18
|
+
|
|
19
|
+
/** The word that stands in for the logo when no art fits. */
|
|
20
|
+
export const LOGO_WORD = "mate";
|
|
21
|
+
|
|
22
|
+
const FALLBACKS: Record<LogoForm, readonly LogoForm[]> = {
|
|
23
|
+
lockup: ["lockup", "symbol"],
|
|
24
|
+
symbol: ["symbol"],
|
|
25
|
+
wordmark: ["wordmark"],
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function asciiWidth(rows: readonly string[]): number {
|
|
29
|
+
return Math.max(...rows.map((row) => row.length));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Whether colour is allowed, following https://no-color.org: any non-empty NO_COLOR disables it. */
|
|
33
|
+
export function colorEnabled(
|
|
34
|
+
isTTY: boolean,
|
|
35
|
+
env: Record<string, string | undefined> = {},
|
|
36
|
+
): boolean {
|
|
37
|
+
return isTTY && !env.NO_COLOR;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Picks the logo encoding for a stream: block art on a TTY (coloured unless NO_COLOR is set),
|
|
42
|
+
* ASCII otherwise, falling back from the lockup to the symbol to the word `mate` as width runs
|
|
43
|
+
* out.
|
|
44
|
+
*/
|
|
45
|
+
export function chooseLogo({ form, columns, isTTY, env = {} }: LogoOptions): LogoChoice {
|
|
46
|
+
for (const candidate of FALLBACKS[form]) {
|
|
47
|
+
const art = LOGO_ART[candidate];
|
|
48
|
+
if (!isTTY) {
|
|
49
|
+
if (asciiWidth(art.ascii) <= columns)
|
|
50
|
+
return { kind: "ascii", form: candidate, rows: art.ascii };
|
|
51
|
+
} else if (art.columns <= columns) {
|
|
52
|
+
return { kind: "block", form: candidate, rows: art.block, color: colorEnabled(isTTY, env) };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return { kind: "word" };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function hexToRgb(hex: string): string {
|
|
59
|
+
const value = Number.parseInt(hex.slice(1), 16);
|
|
60
|
+
return `${(value >> 16) & 255};${(value >> 8) & 255};${value & 255}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const NOTE_ON = `\x1b[38;2;${hexToRgb(BRAND_COLORS.mateGreen.hex)}m`;
|
|
64
|
+
const NOTE_OFF = "\x1b[39m";
|
|
65
|
+
|
|
66
|
+
/** Renders the logo as a string ready to write to a stream, rows separated by newlines. */
|
|
67
|
+
export function renderLogo(options: LogoOptions): string {
|
|
68
|
+
const choice = chooseLogo(options);
|
|
69
|
+
if (choice.kind === "word") return LOGO_WORD;
|
|
70
|
+
if (choice.kind === "ascii") return choice.rows.join("\n");
|
|
71
|
+
|
|
72
|
+
return choice.rows
|
|
73
|
+
.map((row) =>
|
|
74
|
+
row
|
|
75
|
+
.map((segment) =>
|
|
76
|
+
choice.color && segment.kind === "note"
|
|
77
|
+
? `${NOTE_ON}${segment.text}${NOTE_OFF}`
|
|
78
|
+
: segment.text,
|
|
79
|
+
)
|
|
80
|
+
.join(""),
|
|
81
|
+
)
|
|
82
|
+
.join("\n");
|
|
83
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a run of logo cells belongs to. Notes are the three bars of the right pane and the
|
|
3
|
+
* only cells coloured on an unknown background; panes and the wordmark keep the default
|
|
4
|
+
* foreground.
|
|
5
|
+
*/
|
|
6
|
+
export type LogoSegmentKind = "pane" | "note" | "word";
|
|
7
|
+
|
|
8
|
+
/** A run of adjacent cells that share a kind. Spaces between runs are their own segments. */
|
|
9
|
+
export interface LogoSegment {
|
|
10
|
+
kind: LogoSegmentKind | "space";
|
|
11
|
+
text: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type LogoRow = readonly LogoSegment[];
|
|
15
|
+
|
|
16
|
+
/** One logo form in every terminal encoding the kit provides. */
|
|
17
|
+
export interface LogoArt {
|
|
18
|
+
/** Unicode half-block art, one entry per row. */
|
|
19
|
+
readonly block: readonly LogoRow[];
|
|
20
|
+
/** 7-bit ASCII art for streams that are not a TTY. */
|
|
21
|
+
readonly ascii: readonly string[];
|
|
22
|
+
/** Width in columns of the block art. */
|
|
23
|
+
readonly columns: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type LogoForm = "lockup" | "symbol" | "wordmark";
|
|
27
|
+
|
|
28
|
+
/** The SVG markup a web surface embeds, generated verbatim from the kit. */
|
|
29
|
+
export interface BrandSvgs {
|
|
30
|
+
readonly symbolColor: string;
|
|
31
|
+
readonly symbolOnDark: string;
|
|
32
|
+
readonly favicon: string;
|
|
33
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { usage } from "../../usage";
|
|
2
|
+
import { runArtifactArchiveCommand } from "./delivery/archive";
|
|
3
|
+
import { runArtifactLinkCommand } from "./delivery/link";
|
|
2
4
|
import { runArtifactPublishCommand } from "./finish";
|
|
3
5
|
import { runArtifactPendingCommand } from "./pending";
|
|
4
6
|
|
|
@@ -30,6 +32,12 @@ export async function runArtifactCommand(
|
|
|
30
32
|
case "pending":
|
|
31
33
|
await runArtifactPendingCommand(argv);
|
|
32
34
|
return;
|
|
35
|
+
case "link":
|
|
36
|
+
await runArtifactLinkCommand(argv);
|
|
37
|
+
return;
|
|
38
|
+
case "archive":
|
|
39
|
+
await runArtifactArchiveCommand(argv);
|
|
40
|
+
return;
|
|
33
41
|
default:
|
|
34
42
|
console.error(`Unknown artifact command: ${subcommand ?? ""}`);
|
|
35
43
|
console.error(usage());
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import {
|
|
2
|
+
archiveChange,
|
|
3
|
+
type ArchiveChangeDeps,
|
|
4
|
+
type ArchiveResult,
|
|
5
|
+
} from "../../../../lib/delivery/archive-change";
|
|
6
|
+
import type { DeliveryRefusal } from "../../../../lib/delivery/record-delivery";
|
|
7
|
+
import { type BooleanFlagSet, parseFlags, positionalArgs } from "../../../parse-flags";
|
|
8
|
+
import {
|
|
9
|
+
branchFlagsFrom,
|
|
10
|
+
canonicalRepository,
|
|
11
|
+
deliveryEditFrom,
|
|
12
|
+
DIRECT_NEEDS_BRANCH,
|
|
13
|
+
repositoryFlag,
|
|
14
|
+
valueFlagError,
|
|
15
|
+
} from "./branch-flags";
|
|
16
|
+
import { resolveDeliveryEnvironment, type DeliveryEnvironmentDeps } from "./environment";
|
|
17
|
+
import {
|
|
18
|
+
commandOutput,
|
|
19
|
+
renderDelivery,
|
|
20
|
+
sharedRefusalMessage,
|
|
21
|
+
unscopedDeliveryWarning,
|
|
22
|
+
} from "./output";
|
|
23
|
+
|
|
24
|
+
export interface ArchiveCommandDeps
|
|
25
|
+
extends DeliveryEnvironmentDeps, Omit<ArchiveChangeDeps, "onWarnings"> {
|
|
26
|
+
stdout?: (line: string) => void;
|
|
27
|
+
stderr?: (line: string) => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const BOOLEAN_FLAGS: BooleanFlagSet = new Set(["json", "direct", "skip-branch"]);
|
|
31
|
+
|
|
32
|
+
function refusalMessage(refusal: DeliveryRefusal): string {
|
|
33
|
+
if (refusal.reason === "unknown-change") {
|
|
34
|
+
return `mate: no active change named "${refusal.change}" to archive.`;
|
|
35
|
+
}
|
|
36
|
+
return sharedRefusalMessage(refusal);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Warnings are not repeated here: they go to stderr before anything is archived. */
|
|
40
|
+
function renderHuman(result: ArchiveResult): string[] {
|
|
41
|
+
return [
|
|
42
|
+
result.message,
|
|
43
|
+
...result.record.deliveries.map(renderDelivery),
|
|
44
|
+
` delivery: ${result.settlement.status}`,
|
|
45
|
+
];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @command mate artifact archive <change>
|
|
50
|
+
* @description Archives an active change whether or not its delivery is merged, so a
|
|
51
|
+
* feature's later changes build on its canonical specs. Writes overlap, dependency, and
|
|
52
|
+
* incomplete-planning warnings to stderr before archiving, refreshes merge state from the forge when a token
|
|
53
|
+
* is available, always leaves a `delivery.yaml` (unlinked when none was recorded), then
|
|
54
|
+
* runs `openspec archive` with the bundled CLI. Merge state is reported, never a refusal.
|
|
55
|
+
* @flags
|
|
56
|
+
* - `--repository <org/repo>` — the delivery `--branch` or `--not-applicable` records; defaults
|
|
57
|
+
* to the Working Repository.
|
|
58
|
+
* - `--branch <branch>` / `--direct` — record the delivery branch first, as `artifact link`.
|
|
59
|
+
* - `--not-applicable <reason>` — record that no branch delivers this change.
|
|
60
|
+
* - `--skip-branch` — archive with an unlinked record when the user skips the question.
|
|
61
|
+
* - `--json` — emit an {@link ArchiveResult}.
|
|
62
|
+
* @remarks Without a recorded delivery and none of the branch flags, nothing is archived:
|
|
63
|
+
* the result is `needs-branch`, a question rather than a refusal (exit 0), with
|
|
64
|
+
* `change/<change-name>` suggested when that branch exists, so the caller can ask the user.
|
|
65
|
+
*/
|
|
66
|
+
export async function runArtifactArchiveCommand(
|
|
67
|
+
argv: string[],
|
|
68
|
+
deps: ArchiveCommandDeps = {},
|
|
69
|
+
): Promise<void> {
|
|
70
|
+
const { emitOut, emitErr, refuse } = commandOutput(deps);
|
|
71
|
+
const flags = parseFlags(argv, BOOLEAN_FLAGS);
|
|
72
|
+
const flagError = valueFlagError(flags, ["repository", "branch", "not-applicable"]);
|
|
73
|
+
if (flagError) return refuse(flagError);
|
|
74
|
+
const json = flags.json === true;
|
|
75
|
+
const [change] = positionalArgs(argv, BOOLEAN_FLAGS);
|
|
76
|
+
const branchFlags = branchFlagsFrom(flags);
|
|
77
|
+
const { branch, notApplicable } = branchFlags;
|
|
78
|
+
const skip = flags["skip-branch"] === true;
|
|
79
|
+
|
|
80
|
+
if (!change) return refuse("mate: artifact archive requires a change name.");
|
|
81
|
+
if ([branch !== undefined, notApplicable !== undefined, skip].filter(Boolean).length > 1) {
|
|
82
|
+
return refuse("mate: pass only one of --branch, --not-applicable, or --skip-branch.");
|
|
83
|
+
}
|
|
84
|
+
if (branchFlags.direct && branch === undefined) return refuse(DIRECT_NEEDS_BRANCH);
|
|
85
|
+
const requested = repositoryFlag(flags);
|
|
86
|
+
if ("error" in requested) return refuse(requested.error);
|
|
87
|
+
if (requested.repository !== undefined && branch === undefined && notApplicable === undefined) {
|
|
88
|
+
return refuse("mate: --repository names the delivery --branch or --not-applicable records.");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const environment = await resolveDeliveryEnvironment(deps);
|
|
92
|
+
if ("error" in environment) return refuse(environment.error);
|
|
93
|
+
const repository = canonicalRepository(environment, requested.repository);
|
|
94
|
+
const delivery = deliveryEditFrom(branchFlags);
|
|
95
|
+
const outcome = await archiveChange(
|
|
96
|
+
environment,
|
|
97
|
+
{
|
|
98
|
+
change,
|
|
99
|
+
...(repository ? { repository } : {}),
|
|
100
|
+
...(delivery ? { delivery } : {}),
|
|
101
|
+
skipBranch: skip,
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
...deps,
|
|
105
|
+
onWarnings: (warnings, unscoped) => {
|
|
106
|
+
for (const warning of warnings) emitErr(`warning: ${warning.message}`);
|
|
107
|
+
for (const entry of unscoped) emitErr(unscopedDeliveryWarning(change, entry));
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
);
|
|
111
|
+
if ("refusal" in outcome) return refuse(refusalMessage(outcome.refusal));
|
|
112
|
+
|
|
113
|
+
const { result } = outcome;
|
|
114
|
+
if (result.status === "failed") process.exitCode = 1;
|
|
115
|
+
if (json) emitOut(JSON.stringify(result));
|
|
116
|
+
else for (const line of renderHuman(result)) emitOut(line);
|
|
117
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { DeliveryEnvironment } from "../../../../lib/delivery/environment";
|
|
2
|
+
import { sameRepository } from "../../../../lib/delivery/record";
|
|
3
|
+
import type { DeliveryEdit } from "../../../../lib/delivery/record-delivery";
|
|
4
|
+
import { type CliFlags, malformedValueFlag, stringFlag } from "../../../parse-flags";
|
|
5
|
+
|
|
6
|
+
/** `--branch` with `--direct`, or `--not-applicable`, as `artifact link` and `archive` take them. */
|
|
7
|
+
export interface BranchFlags {
|
|
8
|
+
branch?: string;
|
|
9
|
+
direct: boolean;
|
|
10
|
+
notApplicable?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function branchFlagsFrom(flags: CliFlags): BranchFlags {
|
|
14
|
+
return {
|
|
15
|
+
branch: stringFlag(flags.branch),
|
|
16
|
+
direct: flags.direct === true,
|
|
17
|
+
notApplicable: stringFlag(flags["not-applicable"]),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const DIRECT_NEEDS_BRANCH = "mate: --direct records a direct delivery and needs --branch.";
|
|
22
|
+
|
|
23
|
+
/** The delivery edit the branch flags ask for; `--branch` wins over `--not-applicable`. */
|
|
24
|
+
export function deliveryEditFrom(flags: BranchFlags): DeliveryEdit["delivery"] {
|
|
25
|
+
if (flags.branch !== undefined) return { branch: flags.branch, direct: flags.direct };
|
|
26
|
+
if (flags.notApplicable !== undefined) return { notApplicable: flags.notApplicable };
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Refuses a value flag given bare or repeated, which would otherwise read as absent. */
|
|
31
|
+
export function valueFlagError(flags: CliFlags, names: readonly string[]): string | undefined {
|
|
32
|
+
const name = malformedValueFlag(flags, names);
|
|
33
|
+
return name === undefined ? undefined : `mate: --${name} takes exactly one value.`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** `--repository <org/repo>`, or the refusal for a malformed one. */
|
|
37
|
+
export function repositoryFlag(flags: CliFlags): { repository?: string } | { error: string } {
|
|
38
|
+
const repository = stringFlag(flags.repository);
|
|
39
|
+
if (repository !== undefined && !/^[\w.-]+(?:\/[\w.-]+)+$/.test(repository)) {
|
|
40
|
+
return {
|
|
41
|
+
error:
|
|
42
|
+
"mate: --repository expects a repository path such as <org/repo> or <group/subgroup/repo>.",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return repository === undefined ? {} : { repository };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** GitHub slugs are case-insensitive; the Working Repository's own spelling keeps one delivery. */
|
|
49
|
+
export function canonicalRepository(
|
|
50
|
+
environment: DeliveryEnvironment,
|
|
51
|
+
requested: string | undefined,
|
|
52
|
+
): string | undefined {
|
|
53
|
+
const own = environment.workingRepo?.slug;
|
|
54
|
+
return own && requested !== undefined && sameRepository(requested, own) ? own : requested;
|
|
55
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import {
|
|
2
|
+
workingRepoGit,
|
|
3
|
+
configuredProtectedPatterns,
|
|
4
|
+
type WorkingRepoGit,
|
|
5
|
+
} from "../../../../lib/delivery/branches";
|
|
6
|
+
import type { DeliveryEnvironment, WorkingRepoInfo } from "../../../../lib/delivery/environment";
|
|
7
|
+
import { parseRemote, repositoryPath, type ForgeRepo } from "../../../../lib/delivery/forge";
|
|
8
|
+
import { sameRepository } from "../../../../lib/delivery/record";
|
|
9
|
+
import { hasOpenspecCapability } from "../../../../lib/orchestrator/capabilities";
|
|
10
|
+
import {
|
|
11
|
+
resolveForCapability,
|
|
12
|
+
type LaunchContext,
|
|
13
|
+
} from "../../../../lib/orchestrator/framework-context";
|
|
14
|
+
import type { CapabilityConfig, LinkedRepository } from "../../../../lib/orchestrator/types";
|
|
15
|
+
import { WorkingRepoRequiredError } from "../../../../lib/orchestrator/types";
|
|
16
|
+
import { ensureUnambiguousCompanion } from "../../shared/companion-selection";
|
|
17
|
+
|
|
18
|
+
export interface DeliveryEnvironmentDeps {
|
|
19
|
+
ensureUnambiguousCompanion?: (cwd: string) => Promise<boolean>;
|
|
20
|
+
resolveContext?: (cwd: string) => Promise<LaunchContext>;
|
|
21
|
+
loadCapabilities?: (context: LaunchContext) => Promise<CapabilityConfig[]>;
|
|
22
|
+
workingRepoGit?: (repoPath: string) => WorkingRepoGit;
|
|
23
|
+
loadLinkedRepositories?: (context: LaunchContext) => Promise<LinkedRepository[]>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function defaultLoadLinkedRepositories(context: LaunchContext): Promise<LinkedRepository[]> {
|
|
27
|
+
return (await context.workingRepoStore.load()).repos;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function defaultLoadCapabilities(context: LaunchContext): Promise<CapabilityConfig[]> {
|
|
31
|
+
const config = await context.configStore.load();
|
|
32
|
+
return config.capabilities ?? [];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The companion, the Working Repository with its remote identity, and the configured
|
|
37
|
+
* protected-branch patterns. `error` explains why the command cannot run; it is null
|
|
38
|
+
* when the companion check has already reported the problem.
|
|
39
|
+
*/
|
|
40
|
+
export async function resolveDeliveryEnvironment(
|
|
41
|
+
deps: DeliveryEnvironmentDeps,
|
|
42
|
+
): Promise<DeliveryEnvironment | { error: string | null }> {
|
|
43
|
+
const ensureCompanion = deps.ensureUnambiguousCompanion ?? ensureUnambiguousCompanion;
|
|
44
|
+
if (!(await ensureCompanion(process.cwd()))) return { error: null };
|
|
45
|
+
|
|
46
|
+
let context: LaunchContext;
|
|
47
|
+
try {
|
|
48
|
+
context = await (deps.resolveContext ?? ((cwd: string) => resolveForCapability(cwd)))(
|
|
49
|
+
process.cwd(),
|
|
50
|
+
);
|
|
51
|
+
} catch (err) {
|
|
52
|
+
if (err instanceof WorkingRepoRequiredError) return { error: err.message };
|
|
53
|
+
throw err;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const capabilities = await (deps.loadCapabilities ?? defaultLoadCapabilities)(context);
|
|
57
|
+
if (!hasOpenspecCapability(capabilities)) {
|
|
58
|
+
return { error: "mate: the openspec capability must be enabled to record delivery." };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const openGit = deps.workingRepoGit ?? workingRepoGit;
|
|
62
|
+
const repoPath = context.repository?.path ?? process.env.MATE_REPO_PATH;
|
|
63
|
+
let workingRepo: WorkingRepoInfo | null = null;
|
|
64
|
+
if (repoPath) {
|
|
65
|
+
const git = openGit(repoPath);
|
|
66
|
+
const remote = await git.remoteUrl();
|
|
67
|
+
const forgeRepo = remote ? parseRemote(remote) : null;
|
|
68
|
+
workingRepo = {
|
|
69
|
+
path: repoPath,
|
|
70
|
+
slug: forgeRepo ? repositoryPath(forgeRepo) : null,
|
|
71
|
+
forgeRepo,
|
|
72
|
+
git,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let linkedRemotes: Promise<ForgeRepo[]> | undefined;
|
|
77
|
+
// Read once, and only when a repository other than the Working Repository needs a host.
|
|
78
|
+
const linked = (): Promise<ForgeRepo[]> =>
|
|
79
|
+
(linkedRemotes ??= (async () => {
|
|
80
|
+
// An unreadable registry only loses host hints; the Working Repository's host remains.
|
|
81
|
+
const repositories = await (deps.loadLinkedRepositories ?? defaultLoadLinkedRepositories)(
|
|
82
|
+
context,
|
|
83
|
+
).catch(() => []);
|
|
84
|
+
const remotes = await Promise.all(
|
|
85
|
+
repositories
|
|
86
|
+
.filter((repository) => repository.path !== repoPath)
|
|
87
|
+
.map((repository) => openGit(repository.path).remoteUrl()),
|
|
88
|
+
);
|
|
89
|
+
return remotes.flatMap((remote) => (remote ? (parseRemote(remote) ?? []) : []));
|
|
90
|
+
})());
|
|
91
|
+
const hostOf = async (repository: string): Promise<string | null> => {
|
|
92
|
+
const match = (await linked()).find((remote) =>
|
|
93
|
+
sameRepository(repositoryPath(remote), repository),
|
|
94
|
+
);
|
|
95
|
+
return match?.host ?? workingRepo?.forgeRepo?.host ?? null;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
companionPath: context.companionPath,
|
|
100
|
+
workingRepo,
|
|
101
|
+
protectedPatterns: configuredProtectedPatterns(capabilities),
|
|
102
|
+
hostOf,
|
|
103
|
+
};
|
|
104
|
+
}
|