@uniqbit/mate-core 0.15.5 → 0.16.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/.claude-plugin/plugin.json +2 -2
- package/claude-plugin/hooks/hooks.json +3 -6
- package/claude-plugin/hooks/session-guidance.mjs +8 -0
- package/claude-plugin/hooks/ts-loader.mjs +19 -0
- package/package.json +6 -4
- package/src/cli/commands/artifact/artifact.ts +19 -3
- package/src/cli/commands/artifact/finish/command.ts +183 -57
- package/src/cli/commands/artifact/finish/engine.ts +80 -91
- package/src/cli/commands/artifact/finish/finisher.ts +26 -33
- package/src/cli/commands/artifact/finish/git.ts +34 -23
- package/src/cli/commands/artifact/finish/index.ts +9 -3
- package/src/cli/commands/artifact/finish/openspec.ts +225 -97
- package/src/cli/commands/artifact/pending/command.ts +175 -0
- package/src/cli/commands/artifact/pending/discovery.ts +249 -0
- package/src/cli/commands/artifact/pending/index.ts +17 -0
- package/src/cli/commands/cap/index-cmd.ts +9 -1
- package/src/cli/commands/cap/index.ts +2 -6
- package/src/cli/commands/companion/companion.ts +5 -1
- package/src/cli/commands/companion/link.ts +2 -2
- package/src/cli/commands/companion/sync.ts +92 -0
- package/src/cli/commands/doctor.ts +0 -3
- package/src/cli/commands/launch/shared.ts +23 -5
- package/src/cli/commands/report/collector.ts +72 -78
- package/src/cli/commands/report/contract.ts +40 -1
- package/src/cli/commands/report/highlight.ts +27 -0
- package/src/cli/commands/report/index.ts +11 -18
- package/src/cli/commands/report/renderer.ts +199 -2
- package/src/cli/commands/report/types.ts +26 -1
- package/src/cli/commands/shared/companion-selection.ts +107 -10
- package/src/cli/commands/studio/areas.ts +68 -0
- package/src/cli/commands/studio/index.ts +69 -0
- package/src/cli/commands/studio/inventory.ts +55 -0
- package/src/cli/commands/studio/mate-inventory.ts +43 -0
- package/src/cli/commands/studio/openspec-cli.ts +198 -0
- package/src/cli/commands/studio/payload.ts +184 -0
- package/src/cli/commands/studio/routes.ts +2 -0
- package/src/cli/commands/studio/selection.ts +61 -0
- package/src/cli/commands/studio/server.ts +201 -0
- package/src/cli/commands/studio/snapshot.ts +63 -0
- package/src/cli/commands/studio/topology.ts +199 -0
- package/src/cli/commands/studio/views/client.ts +197 -0
- package/src/cli/commands/studio/views/companion-picker.tsx +91 -0
- package/src/cli/commands/studio/views/companion-selector.tsx +90 -0
- package/src/cli/commands/studio/views/dashboard/changes.tsx +107 -0
- package/src/cli/commands/studio/views/dashboard/index.tsx +19 -0
- package/src/cli/commands/studio/views/document.tsx +256 -0
- package/src/cli/commands/studio/views/error.tsx +20 -0
- package/src/cli/commands/studio/views/model.ts +34 -0
- package/src/cli/commands/studio/views/pairings.tsx +38 -0
- package/src/cli/commands/studio/views/skills/index.tsx +87 -0
- package/src/cli/commands/studio/views/specs/index.tsx +101 -0
- package/src/cli/commands/studio/views/styles.ts +389 -0
- package/src/cli/commands/studio/views/warnings.tsx +21 -0
- package/src/cli/commands/studio/views/workflow/index.tsx +21 -0
- package/src/cli/commands/studio/views/workflow/steps.ts +329 -0
- package/src/cli/commands/studio/views/workflow/transcript.tsx +190 -0
- package/src/cli/commands/unwrap.ts +70 -0
- package/src/cli/commands/wrap.ts +164 -0
- package/src/cli/main.ts +68 -19
- package/src/cli/parse-flags.ts +36 -11
- package/src/cli/usage.ts +12 -3
- package/src/framework.ts +1 -7
- package/src/hooks/session-banner.ts +64 -11
- package/src/hooks/session-guidance.ts +40 -0
- package/src/hooks/validate-artifact-path.ts +108 -35
- package/src/lib/fs-utils.ts +9 -0
- package/src/lib/install.ts +33 -0
- package/src/lib/orchestrator/adapters/base.ts +14 -125
- package/src/lib/orchestrator/adapters/claude.ts +0 -11
- package/src/lib/orchestrator/adapters/opencode.ts +2 -32
- package/src/lib/orchestrator/companion-git-sync.ts +94 -84
- package/src/lib/orchestrator/config-store.ts +2 -21
- package/src/lib/orchestrator/editor.ts +12 -22
- package/src/lib/orchestrator/framework-context.ts +17 -6
- package/src/lib/orchestrator/global-config-store.ts +1 -1
- package/src/lib/orchestrator/launcher.ts +97 -7
- package/src/lib/orchestrator/opencode-guidance.ts +4 -56
- package/src/lib/orchestrator/projection-claude-entry.ts +198 -0
- package/src/lib/orchestrator/projection-claude-skills.ts +120 -0
- package/src/lib/orchestrator/projection-companion-link.ts +62 -0
- package/src/lib/orchestrator/projection-entries.ts +377 -0
- package/src/lib/orchestrator/projection-record.ts +56 -0
- package/src/lib/orchestrator/projection-runtime-documents.ts +424 -0
- package/src/lib/orchestrator/projection-types.ts +169 -0
- package/src/lib/orchestrator/repo-local-registry.ts +37 -133
- package/src/lib/orchestrator/repo-local-store.ts +96 -0
- package/src/lib/orchestrator/setup-compatibilities.ts +1 -9
- package/src/lib/orchestrator/types.ts +1 -0
- package/src/lib/orchestrator/working-repo-projection.ts +366 -0
- package/src/lib/orchestrator/workspace-inventory.ts +1 -1
- package/src/lib/package-paths.ts +11 -1
- package/src/opencode/companion-hooks.ts +89 -245
- package/src/opencode/companion-policy.ts +35 -10
- package/src/opencode/index.ts +1 -0
- package/src/opencode/projected-guidance.ts +56 -0
- package/src/opencode/tui.tsx +13 -4
- package/src/playbooks/companion-guidance.ts +32 -116
- package/src/plugins.ts +0 -1
- package/src/runtime/companion-git-state.ts +156 -0
- package/src/runtime/companion-git.ts +203 -0
- package/src/runtime/companion-guidance.ts +222 -0
- package/src/runtime/companion-sync.ts +298 -0
- package/src/runtime/env-names.ts +30 -0
- package/src/runtime/env.ts +67 -35
- package/src/runtime/framework.ts +10 -0
- package/src/runtime/freshness.ts +58 -0
- package/src/runtime/index.ts +104 -0
- package/src/runtime/install.ts +30 -0
- package/src/runtime/policy.ts +66 -0
- package/src/runtime/projected-guidance.ts +45 -0
- package/src/runtime/projection.ts +224 -0
- package/src/runtime/repo-local.ts +64 -0
- package/src/templates/capabilities/openspec-cap/mate-minimal/schema.yaml +56 -0
- package/src/templates/capabilities/openspec-cap/mate-minimal/templates/spec.md +48 -0
- package/src/templates/capabilities/openspec-cap/mate-minimal/templates/tasks.md +22 -0
- package/src/templates/capabilities/openspec-cap/mate-v1/schema.yaml +31 -90
- package/src/templates/capabilities/openspec-cap/mate-v1/templates/design.md +3 -0
- package/src/templates/capabilities/openspec-cap/mate-v1/templates/explore-brief.md +6 -6
- package/src/templates/capabilities/openspec-cap/mate-v1/templates/spec.md +3 -5
- package/src/templates/capabilities/openspec-cap/mate-v1/templates/tasks.md +3 -0
- package/src/templates/capabilities/openspec-cap/openspec-conventions.yaml +30 -0
- package/src/templates/capabilities/react-doctor/claude/hooks/react-doctor.sh +2 -2
- package/src/templates/mate-skills/agents/mate-artifact-publish/SKILL.md +185 -0
- package/src/templates/mate-skills/agents/mate-artifact-publish/references/openspec.md +227 -0
- package/src/templates/mate-skills/agents/mate-domain-modeling/SKILL.md +68 -0
- package/src/templates/mate-skills/agents/mate-domain-modeling/references/ADR-FORMAT.md +9 -0
- package/src/templates/mate-skills/agents/mate-domain-modeling/references/CONTEXT-FORMAT.md +18 -0
- package/src/templates/mate-skills/agents/mate-grill-me/SKILL.md +14 -0
- package/src/templates/mate-skills/agents/mate-grill-with-docs/SKILL.md +29 -0
- package/src/templates/mate-skills/agents/mate-grilling/SKILL.md +49 -0
- package/src/templates/mate-skills/agents/mate-interview-me/SKILL.md +147 -0
- package/src/templates/{capabilities/openspec-cap/mate-skills → mate-skills}/agents/mate-openspec-backfill/SKILL.md +4 -2
- package/src/templates/mate-skills/agents/mate-show-me/SKILL.md +139 -0
- package/src/templates/mate-skills/agents/mate-simplify-code/SKILL.md +503 -0
- package/src/templates/mate-skills/claude/mate-artifact-publish/SKILL.md +185 -0
- package/src/templates/mate-skills/claude/mate-artifact-publish/references/openspec.md +227 -0
- package/src/templates/mate-skills/claude/mate-domain-modeling/SKILL.md +68 -0
- package/src/templates/mate-skills/claude/mate-domain-modeling/references/ADR-FORMAT.md +9 -0
- package/src/templates/mate-skills/claude/mate-domain-modeling/references/CONTEXT-FORMAT.md +18 -0
- package/src/templates/mate-skills/claude/mate-grill-me/SKILL.md +14 -0
- package/src/templates/mate-skills/claude/mate-grill-with-docs/SKILL.md +29 -0
- package/src/templates/mate-skills/claude/mate-grilling/SKILL.md +49 -0
- package/src/templates/mate-skills/claude/mate-interview-me/SKILL.md +147 -0
- package/src/templates/mate-skills/claude/mate-openspec-backfill/SKILL.md +67 -0
- package/src/templates/mate-skills/claude/mate-show-me/SKILL.md +139 -0
- package/src/templates/mate-skills/claude/mate-simplify-code/SKILL.md +503 -0
- package/src/templates/report-assets/README.md +32 -0
- package/src/templates/report-assets/mermaid.LICENSE +21 -0
- package/src/templates/report-assets/mermaid.min.js +4376 -0
- package/src/templates/root/TEMPLATE_CLAUDE.md +1 -9
- package/src/tools/setup/__snapshots__/runtime-surface-golden.test.ts.snap +1476 -197
- package/src/tools/setup/capabilities/graphify.ts +16 -7
- package/src/tools/setup/capabilities/openspec.ts +63 -56
- package/src/tools/setup/capabilities/tokensave.ts +47 -0
- package/src/tools/setup/engine.ts +34 -6
- package/src/tools/setup/mate.ts +42 -13
- package/src/tools/setup/plugin.ts +9 -0
- package/src/tools/setup/plugins/guidance.ts +11 -1
- package/src/tools/setup/providers/claude-format.ts +49 -4
- package/src/tools/setup/providers/claude-plugin-hooks.ts +117 -0
- package/src/tools/setup/providers/claude.ts +55 -220
- package/src/tools/setup/providers/opencode.ts +41 -14
- package/src/tools/setup/runtime-documents.ts +174 -0
- package/src/tools/setup/surface-target.ts +50 -0
- package/src/tools/setup/working-repo-cleanup.ts +33 -26
- package/src/tools/setup/working-repo-local-state.ts +21 -1
- package/src/tools/setup.ts +25 -3
- package/wrappers/bin/graphify +57 -8
- package/wrappers/bin/openspec +50 -3
- package/claude-plugin/hooks/artifact-finish-nudge.mjs +0 -8
- package/src/cli/commands/cap/headroom.ts +0 -52
- package/src/cli/commands/workspace/list.ts +0 -25
- package/src/cli/commands/workspace/materialize.ts +0 -46
- package/src/cli/commands/workspace/workspace.ts +0 -22
- package/src/hooks/artifact-finish-nudge.ts +0 -244
- package/src/lib/orchestrator/headroom/proxy.ts +0 -116
- package/src/lib/orchestrator/workspace-materialize.ts +0 -80
- package/src/templates/capabilities/openspec-cap/mate-skills/agents/mate-artifact-finish/SKILL.md +0 -51
- package/src/templates/capabilities/openspec-cap/mate-skills/agents/mate-artifact-finish/references/openspec.md +0 -134
- package/src/templates/capabilities/openspec-cap/mate-skills/claude/mate-artifact-finish/SKILL.md +0 -58
- package/src/templates/capabilities/openspec-cap/mate-skills/claude/mate-artifact-finish/references/openspec.md +0 -139
- package/src/tools/setup/capabilities/headroom.ts +0 -57
- /package/src/cli/commands/{workspace → companion}/open.ts +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "mate",
|
|
3
|
-
"description": "Mate-managed Claude Code hooks: artifact-path guardrail, session banner, and
|
|
2
|
+
"name": "mate-claude-plugin",
|
|
3
|
+
"description": "Mate-managed Claude Code hooks: artifact-path guardrail, session banner, and companion guidance. Ships inside @uniqbit/mate-core and is never published separately. A managed launch loads it with --plugin-dir; a wrapped repository instead inlines the guard and guidance commands into its settings, with ${CLAUDE_PLUGIN_ROOT} already resolved."
|
|
4
4
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"description": "Mate hooks — PreToolUse artifact-path guardrail, SessionStart banner
|
|
2
|
+
"description": "Mate hooks — PreToolUse artifact-path guardrail, SessionStart banner and companion guidance",
|
|
3
3
|
"hooks": {
|
|
4
4
|
"PreToolUse": [
|
|
5
5
|
{
|
|
@@ -20,15 +20,12 @@
|
|
|
20
20
|
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/session-banner.mjs\""
|
|
21
21
|
}
|
|
22
22
|
]
|
|
23
|
-
}
|
|
24
|
-
],
|
|
25
|
-
"PostToolUse": [
|
|
23
|
+
},
|
|
26
24
|
{
|
|
27
|
-
"matcher": "Bash",
|
|
28
25
|
"hooks": [
|
|
29
26
|
{
|
|
30
27
|
"type": "command",
|
|
31
|
-
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/
|
|
28
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/session-guidance.mjs\""
|
|
32
29
|
}
|
|
33
30
|
]
|
|
34
31
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Thin shim: hook logic lives in src/hooks/session-guidance.ts, loaded through
|
|
3
|
+
// ts-loader.mjs because the installed plugin sits inside node_modules where
|
|
4
|
+
// node's native type stripping is disabled.
|
|
5
|
+
import "./ts-loader.mjs";
|
|
6
|
+
|
|
7
|
+
const { run } = await import("../../src/hooks/session-guidance.ts");
|
|
8
|
+
process.exitCode = run();
|
|
@@ -15,7 +15,26 @@ process.emitWarning = (warning, ...args) => {
|
|
|
15
15
|
emitWarning.call(process, warning, ...args);
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
// Source imports are extensionless, which node's ESM resolver refuses. Probe
|
|
19
|
+
// the TypeScript candidates the type-checker resolves so a hook module can
|
|
20
|
+
// import the runtime subpath the same way the rest of the source tree does.
|
|
21
|
+
const TS_CANDIDATE_SUFFIXES = [".ts", ".tsx", "/index.ts", "/index.tsx"];
|
|
22
|
+
|
|
18
23
|
registerHooks({
|
|
24
|
+
resolve(specifier, context, next) {
|
|
25
|
+
if (!specifier.startsWith(".") || /\.[cm]?[jt]sx?$/.test(specifier)) {
|
|
26
|
+
return next(specifier, context);
|
|
27
|
+
}
|
|
28
|
+
const base = new URL(specifier, context.parentURL);
|
|
29
|
+
for (const suffix of TS_CANDIDATE_SUFFIXES) {
|
|
30
|
+
const candidate = new URL(`${base.href}${suffix}`);
|
|
31
|
+
if (fs.existsSync(fileURLToPath(candidate))) {
|
|
32
|
+
return { url: candidate.href, format: "module", shortCircuit: true };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return next(specifier, context);
|
|
36
|
+
},
|
|
37
|
+
|
|
19
38
|
load(url, context, next) {
|
|
20
39
|
if (!url.endsWith(".ts")) return next(url, context);
|
|
21
40
|
const source = fs.readFileSync(fileURLToPath(url), "utf8");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniqbit/mate-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0-canary.0",
|
|
4
4
|
"description": "Core framework and plugin APIs for Mate.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"files": [
|
|
@@ -27,17 +27,19 @@
|
|
|
27
27
|
"typecheck": "bunx tsc --noEmit -p ../../tsconfig.json",
|
|
28
28
|
"lint": "oxlint src/",
|
|
29
29
|
"lint:fix": "oxlint src/ --fix",
|
|
30
|
-
"format": "oxfmt src/",
|
|
31
|
-
"format:check": "oxfmt src/ --check"
|
|
30
|
+
"format": "oxfmt src/ \"!**/*.generated.*\" \"!**/report-assets/**\"",
|
|
31
|
+
"format:check": "oxfmt src/ \"!**/*.generated.*\" \"!**/report-assets/**\" --check"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@opencode-ai/plugin": "^1.18.3",
|
|
35
35
|
"@opentui/core": "^0.4.5",
|
|
36
36
|
"@opentui/keymap": "^0.4.5",
|
|
37
37
|
"@opentui/solid": "^0.4.5",
|
|
38
|
+
"@tanstack/highlight": "0.0.10",
|
|
39
|
+
"hono": "^4.13.5",
|
|
38
40
|
"ink": "^7.1.0",
|
|
39
41
|
"react": "^19.2.7",
|
|
40
|
-
"react-doctor": "0.
|
|
42
|
+
"react-doctor": "0.9.13",
|
|
41
43
|
"semver": "^7.8.5",
|
|
42
44
|
"yaml": "^2.9.0"
|
|
43
45
|
},
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { usage } from "../../usage";
|
|
2
|
-
import {
|
|
2
|
+
import { runArtifactPublishCommand } from "./finish";
|
|
3
|
+
import { runArtifactPendingCommand } from "./pending";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Pre-rename spelling of `publish`. Kept so companions whose materialized skills were
|
|
7
|
+
* written before the rename keep working until they re-sync.
|
|
8
|
+
*/
|
|
9
|
+
const DEPRECATED_PUBLISH_ALIAS = "finish";
|
|
3
10
|
|
|
4
11
|
/**
|
|
5
12
|
* @command mate artifact <subcommand>
|
|
@@ -11,8 +18,17 @@ export async function runArtifactCommand(
|
|
|
11
18
|
argv: string[],
|
|
12
19
|
): Promise<void> {
|
|
13
20
|
switch (subcommand) {
|
|
14
|
-
case
|
|
15
|
-
|
|
21
|
+
case DEPRECATED_PUBLISH_ALIAS:
|
|
22
|
+
console.error(
|
|
23
|
+
"mate: `artifact finish` is deprecated and will be removed; use `artifact publish`.",
|
|
24
|
+
);
|
|
25
|
+
await runArtifactPublishCommand(argv);
|
|
26
|
+
return;
|
|
27
|
+
case "publish":
|
|
28
|
+
await runArtifactPublishCommand(argv);
|
|
29
|
+
return;
|
|
30
|
+
case "pending":
|
|
31
|
+
await runArtifactPendingCommand(argv);
|
|
16
32
|
return;
|
|
17
33
|
default:
|
|
18
34
|
console.error(`Unknown artifact command: ${subcommand ?? ""}`);
|
|
@@ -2,37 +2,50 @@ import { resolveForCapability } from "../../../../lib/orchestrator/framework-con
|
|
|
2
2
|
import type { LaunchContext } from "../../../../lib/orchestrator/framework-context";
|
|
3
3
|
import type { CapabilityConfig } from "../../../../lib/orchestrator/types";
|
|
4
4
|
import { WorkingRepoRequiredError } from "../../../../lib/orchestrator/types";
|
|
5
|
-
import { parseFlags } from "../../../parse-flags";
|
|
5
|
+
import { type BooleanFlagSet, parseFlags } from "../../../parse-flags";
|
|
6
6
|
import { ensureUnambiguousCompanion } from "../../shared/companion-selection";
|
|
7
|
-
import {
|
|
8
|
-
import type
|
|
7
|
+
import { discoverArchives, pendingArchives } from "../pending/discovery";
|
|
8
|
+
import { type FinishResult, runFinishEngine } from "./engine";
|
|
9
|
+
import type { ArtifactFinisher, FinishContext, FinisherFactory } from "./finisher";
|
|
9
10
|
import { defaultGitOps, type GitOps } from "./git";
|
|
11
|
+
import { openspecSpecsFinisher, resolveDriftedSpecs } from "./openspec";
|
|
10
12
|
import { DEFAULT_FINISHER_TYPE, knownFinisherTypes, selectFinisher } from "./registry";
|
|
11
13
|
|
|
12
|
-
export interface
|
|
14
|
+
export interface PublishCommandDeps {
|
|
13
15
|
ensureUnambiguousCompanion?: (cwd: string) => Promise<boolean>;
|
|
14
16
|
resolveContext?: (cwd: string) => Promise<LaunchContext>;
|
|
15
17
|
loadCapabilities?: (context: LaunchContext) => Promise<CapabilityConfig[]>;
|
|
16
18
|
selectFinisher?: (type: string) => FinisherFactory | undefined;
|
|
17
19
|
git?: (companionPath: string, workingRepoPath?: string) => GitOps;
|
|
20
|
+
/** Injectable clock; a spec publication's anchor is the calendar date it runs on. */
|
|
21
|
+
now?: () => Date;
|
|
22
|
+
/** Injectable spec-publication finisher; mirrors `selectFinisher` for the other unit. */
|
|
23
|
+
specsFinisher?: (context: FinishContext, paths: string[], now: () => Date) => ArtifactFinisher;
|
|
18
24
|
stdout?: (line: string) => void;
|
|
19
25
|
stderr?: (line: string) => void;
|
|
20
26
|
}
|
|
21
27
|
|
|
22
|
-
|
|
28
|
+
/** Presence-only flags; every other `--flag` consumes the following token as its value. */
|
|
29
|
+
const BOOLEAN_FLAGS: BooleanFlagSet = new Set(["no-push", "json", "specs", "all"]);
|
|
23
30
|
|
|
24
|
-
/**
|
|
25
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Every non-flag token, in order. Consumes the value of a value-taking flag exactly as
|
|
33
|
+
* {@link parseFlags} does, so the positionals and the flags can never disagree about
|
|
34
|
+
* which token belongs to whom.
|
|
35
|
+
*/
|
|
36
|
+
function positionalNames(argv: string[]): string[] {
|
|
37
|
+
const names: string[] = [];
|
|
26
38
|
for (let index = 0; index < argv.length; index += 1) {
|
|
27
39
|
const token = argv[index];
|
|
28
|
-
if (
|
|
29
|
-
|
|
40
|
+
if (!token.startsWith("--")) {
|
|
41
|
+
names.push(token);
|
|
30
42
|
continue;
|
|
31
43
|
}
|
|
32
|
-
|
|
33
|
-
|
|
44
|
+
const key = token.slice(2);
|
|
45
|
+
if (key.includes("=") || BOOLEAN_FLAGS.has(key)) continue;
|
|
46
|
+
index += 1;
|
|
34
47
|
}
|
|
35
|
-
return
|
|
48
|
+
return names;
|
|
36
49
|
}
|
|
37
50
|
|
|
38
51
|
async function defaultLoadCapabilities(context: LaunchContext): Promise<CapabilityConfig[]> {
|
|
@@ -40,41 +53,106 @@ async function defaultLoadCapabilities(context: LaunchContext): Promise<Capabili
|
|
|
40
53
|
return config.capabilities ?? [];
|
|
41
54
|
}
|
|
42
55
|
|
|
56
|
+
/** A publication the pipeline never ran: nothing was resolved, committed, tagged, or pushed. */
|
|
57
|
+
function nothingToPublish(name: string, message: string): FinishResult {
|
|
58
|
+
return {
|
|
59
|
+
type: DEFAULT_FINISHER_TYPE,
|
|
60
|
+
name,
|
|
61
|
+
anchorName: null,
|
|
62
|
+
tag: null,
|
|
63
|
+
resumed: false,
|
|
64
|
+
step: "done",
|
|
65
|
+
status: "skipped",
|
|
66
|
+
conflictedPaths: [],
|
|
67
|
+
local: { committed: false, tagged: false, pushed: false },
|
|
68
|
+
message,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function refusalResult(type: string, name: string, message: string): FinishResult {
|
|
73
|
+
return {
|
|
74
|
+
type,
|
|
75
|
+
name,
|
|
76
|
+
anchorName: null,
|
|
77
|
+
tag: null,
|
|
78
|
+
resumed: false,
|
|
79
|
+
step: "resolve",
|
|
80
|
+
status: "error",
|
|
81
|
+
conflictedPaths: [],
|
|
82
|
+
local: { committed: false, tagged: false, pushed: false },
|
|
83
|
+
message,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
43
87
|
/**
|
|
44
|
-
* @command mate artifact
|
|
45
|
-
* @description
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
88
|
+
* @command mate artifact publish <target>
|
|
89
|
+
* @description Publishes an already-produced artifact. Resolves the launch context for the
|
|
90
|
+
* current working repo, loads the enabled capabilities, and runs {@link runFinishEngine} to
|
|
91
|
+
* commit/tag/push via {@link GitOps}. Two publication units exist: an archived change,
|
|
92
|
+
* named positionally, and a spec publication of drifted canonical specs, selected with
|
|
93
|
+
* `--specs`. Archiving is a precondition performed by the archive workflow; this command
|
|
94
|
+
* never archives.
|
|
51
95
|
* @flags
|
|
52
|
-
* - `<
|
|
53
|
-
*
|
|
54
|
-
* - `--
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
96
|
+
* - `<target>` — a dated archive anchor (`YYYY-MM-DD-<name>`), or a change name matching
|
|
97
|
+
* exactly one archive. Mutually exclusive with `--specs` and `--all`.
|
|
98
|
+
* - `--specs [<path>...]` — publish drifted canonical specs as their own publication under a
|
|
99
|
+
* new `openspec/specs/<date>-<specs>` tag. Bare, it publishes every drifted spec; with paths, only
|
|
100
|
+
* those. A bare name alongside it is a change target and is refused.
|
|
101
|
+
* - `--all` — publish every pending change in discovery order, then one spec publication for
|
|
102
|
+
* the drift that remains. Halts at the first conflict or error; with `--json` it emits a
|
|
103
|
+
* single array of results in execution order.
|
|
104
|
+
* - `--type <type>` — finisher type to use; see `knownFinisherTypes()`. `--specs` and
|
|
105
|
+
* `--all` are openspec-only.
|
|
106
|
+
* - `--no-push` — commit and tag locally but skip the remote sync and push.
|
|
107
|
+
* - `--json` — emit machine-readable JSON instead of human-readable text.
|
|
108
|
+
* @remarks No-ops (with a message on stderr) when the selected finisher is disabled for the
|
|
109
|
+
* repo's configured capabilities — nothing is resolved or mutated in that case.
|
|
60
110
|
*/
|
|
61
|
-
export async function
|
|
111
|
+
export async function runArtifactPublishCommand(
|
|
62
112
|
argv: string[],
|
|
63
|
-
deps:
|
|
113
|
+
deps: PublishCommandDeps = {},
|
|
64
114
|
): Promise<void> {
|
|
65
115
|
const emitOut = deps.stdout ?? ((line: string) => process.stdout.write(`${line}\n`));
|
|
66
116
|
const emitErr = deps.stderr ?? ((line: string) => process.stderr.write(`${line}\n`));
|
|
67
117
|
|
|
68
|
-
const flags = parseFlags(argv);
|
|
69
|
-
const force = flags.force === true;
|
|
118
|
+
const flags = parseFlags(argv, BOOLEAN_FLAGS);
|
|
70
119
|
const noPush = flags["no-push"] === true;
|
|
71
120
|
const json = flags.json === true;
|
|
72
121
|
const type = typeof flags.type === "string" ? flags.type : DEFAULT_FINISHER_TYPE;
|
|
73
|
-
const
|
|
122
|
+
const specsMode = flags.specs === true;
|
|
123
|
+
const allMode = flags.all === true;
|
|
124
|
+
const positionals = positionalNames(argv);
|
|
74
125
|
|
|
75
|
-
|
|
76
|
-
|
|
126
|
+
/** With `--specs`, a path narrows the publication; a bare name is a change target. */
|
|
127
|
+
const specPaths = specsMode ? positionals.filter((token) => token.includes("/")) : [];
|
|
128
|
+
const changeTargets = specsMode
|
|
129
|
+
? positionals.filter((token) => !token.includes("/"))
|
|
130
|
+
: positionals;
|
|
131
|
+
|
|
132
|
+
/** Argument refusals go to stderr in every mode: nothing was resolved to report on. */
|
|
133
|
+
const refuse = (message: string): void => {
|
|
134
|
+
emitErr(message);
|
|
77
135
|
process.exitCode = 1;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
if (allMode && (specsMode || positionals.length > 0)) {
|
|
139
|
+
refuse(
|
|
140
|
+
"mate: --all already publishes both pending changes and drifted specs; drop the extra target.",
|
|
141
|
+
);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (specsMode && changeTargets.length > 0) {
|
|
145
|
+
refuse(
|
|
146
|
+
`mate: "${changeTargets[0]}" is a change target and --specs publishes canonical specs; they are different publication units. Pass one or the other.`,
|
|
147
|
+
);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (!allMode && !specsMode && positionals.length === 0) {
|
|
151
|
+
refuse("mate: artifact publish requires an archive anchor or change name, --specs, or --all.");
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if ((allMode || specsMode) && type !== DEFAULT_FINISHER_TYPE) {
|
|
155
|
+
refuse(`mate: --specs and --all are only available for the ${DEFAULT_FINISHER_TYPE} type.`);
|
|
78
156
|
return;
|
|
79
157
|
}
|
|
80
158
|
|
|
@@ -129,32 +207,80 @@ export async function runArtifactFinishCommand(
|
|
|
129
207
|
context.repository?.path ?? process.env.MATE_REPO_PATH,
|
|
130
208
|
);
|
|
131
209
|
} catch (err) {
|
|
132
|
-
const message = `mate:
|
|
210
|
+
const message = `mate: publish Git guard rejected the target: ${String(err)}`;
|
|
211
|
+
if (json) emitOut(JSON.stringify(refusalResult(type, positionals[0] ?? "", message)));
|
|
212
|
+
else emitErr(message);
|
|
213
|
+
process.exitCode = 1;
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const now = deps.now ?? (() => new Date());
|
|
218
|
+
/**
|
|
219
|
+
* Under `--all --json` the array is the whole output, so the per-publication emission is
|
|
220
|
+
* suppressed; every other mode lets the engine report each publication as it finishes.
|
|
221
|
+
*/
|
|
222
|
+
const quiet = allMode && json;
|
|
223
|
+
const engineDeps = {
|
|
224
|
+
git,
|
|
225
|
+
json: !allMode && json,
|
|
226
|
+
stdout: quiet ? (): void => {} : emitOut,
|
|
227
|
+
stderr: quiet ? (): void => {} : emitErr,
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const runChange = (target: string): Promise<FinishResult> =>
|
|
231
|
+
runFinishEngine(finisher, { name: target, noPush }, engineDeps);
|
|
232
|
+
|
|
233
|
+
const runSpecs = async (requested: string[]): Promise<FinishResult | null> => {
|
|
234
|
+
const drift = await resolveDriftedSpecs(context.companionPath, git, requested);
|
|
235
|
+
if (!drift.ok) {
|
|
236
|
+
if (!quiet && !json) emitErr(drift.message);
|
|
237
|
+
process.exitCode = 1;
|
|
238
|
+
return refusalResult(type, "--specs", drift.message);
|
|
239
|
+
}
|
|
240
|
+
if (drift.paths.length === 0) return null;
|
|
241
|
+
const makeSpecsFinisher = deps.specsFinisher ?? openspecSpecsFinisher;
|
|
242
|
+
const specsFinisher = makeSpecsFinisher(finishContext, drift.paths, now);
|
|
243
|
+
return runFinishEngine(specsFinisher, { name: "--specs", noPush }, engineDeps);
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const halted = (result: FinishResult): boolean =>
|
|
247
|
+
result.status === "conflict" || result.status === "error";
|
|
248
|
+
|
|
249
|
+
if (allMode) {
|
|
250
|
+
const results: FinishResult[] = [];
|
|
251
|
+
const changed = await git.changedPaths();
|
|
252
|
+
const changeKinds = (await git.changedPathKinds?.()) ?? {};
|
|
253
|
+
const archives = await discoverArchives(context.companionPath, changed, changeKinds);
|
|
254
|
+
|
|
255
|
+
for (const entry of pendingArchives(archives)) {
|
|
256
|
+
const result = await runChange(entry.anchor);
|
|
257
|
+
results.push(result);
|
|
258
|
+
if (halted(result)) {
|
|
259
|
+
if (json) emitOut(JSON.stringify(results));
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Drift is recomputed: a spec a change just committed is no longer drifted. */
|
|
265
|
+
const specResult = await runSpecs([]);
|
|
266
|
+
if (specResult) results.push(specResult);
|
|
267
|
+
|
|
268
|
+
if (json) emitOut(JSON.stringify(results));
|
|
269
|
+
else if (results.length === 0) emitOut("Nothing to publish.");
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (specsMode) {
|
|
274
|
+
const result = await runSpecs(specPaths);
|
|
275
|
+
const emitted =
|
|
276
|
+
result ?? nothingToPublish("--specs", "No drifted canonical specs; nothing to publish.");
|
|
133
277
|
if (json) {
|
|
134
|
-
emitOut(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
name,
|
|
138
|
-
anchorName: null,
|
|
139
|
-
tag: null,
|
|
140
|
-
resumed: false,
|
|
141
|
-
step: "commit",
|
|
142
|
-
status: "error",
|
|
143
|
-
conflictedPaths: [],
|
|
144
|
-
local: { committed: false, tagged: false, pushed: false },
|
|
145
|
-
message,
|
|
146
|
-
}),
|
|
147
|
-
);
|
|
148
|
-
} else {
|
|
149
|
-
emitErr(message);
|
|
278
|
+
if (result === null) emitOut(JSON.stringify(emitted));
|
|
279
|
+
} else if (result === null) {
|
|
280
|
+
emitOut(emitted.message);
|
|
150
281
|
}
|
|
151
|
-
process.exitCode = 1;
|
|
152
282
|
return;
|
|
153
283
|
}
|
|
154
284
|
|
|
155
|
-
await
|
|
156
|
-
finisher,
|
|
157
|
-
{ name, force, noPush },
|
|
158
|
-
{ git, json, stdout: emitOut, stderr: emitErr },
|
|
159
|
-
);
|
|
285
|
+
await runChange(positionals[0]);
|
|
160
286
|
}
|