@geminix/gxpm 0.1.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/AGENTS.md +148 -0
- package/CANON.md +53 -0
- package/CLAUDE.md +60 -0
- package/CONTEXT.md +49 -0
- package/DEBUG.md +59 -0
- package/ISSUE_CONTEXT.md +25 -0
- package/README.md +143 -0
- package/VERSION +1 -0
- package/agents/cleanup-auditor/cleanup-auditor.md +56 -0
- package/agents/grill-master.md +26 -0
- package/agents/implementer.md +32 -0
- package/agents/review-army/accessibility-reviewer.md +54 -0
- package/agents/review-army/code-quality-reviewer.md +54 -0
- package/agents/review-army/security-reviewer.md +56 -0
- package/agents/review-army/spec-compliance-reviewer.md +51 -0
- package/agents/review-army/test-reviewer.md +55 -0
- package/agents/reviewer.md +59 -0
- package/agents/ship-audit-army/docs-auditor.md +53 -0
- package/agents/ship-audit-army/performance-auditor.md +52 -0
- package/agents/ship-audit-army/security-auditor.md +52 -0
- package/agents/specifier.md +55 -0
- package/agents/triage-officer.md +27 -0
- package/bin/gxpm +17 -0
- package/bin/gxpm-browser +17 -0
- package/bin/gxpm-config +15 -0
- package/bin/gxpm-eval +13 -0
- package/bin/gxpm-global-discover +15 -0
- package/bin/gxpm-init +38 -0
- package/bin/gxpm-investigate +194 -0
- package/bin/gxpm-uninstall +15 -0
- package/bin/gxpm-update-check +165 -0
- package/commands/build.md +40 -0
- package/commands/help.md +53 -0
- package/commands/plan.md +34 -0
- package/commands/refine.md +46 -0
- package/commands/review.md +34 -0
- package/commands/ship.md +37 -0
- package/core/ac-check.ts +20 -0
- package/core/agent-runtime.ts +363 -0
- package/core/artifact-validator.ts +151 -0
- package/core/artifacts.ts +313 -0
- package/core/autopilot.ts +250 -0
- package/core/capabilities.ts +779 -0
- package/core/checkpoint.ts +370 -0
- package/core/cleanup.ts +32 -0
- package/core/command-probe.ts +82 -0
- package/core/config.ts +533 -0
- package/core/contracts/behavior-spec.schema.ts +38 -0
- package/core/contracts/converter.ts +61 -0
- package/core/contracts/host.ts +43 -0
- package/core/converters/converter.ts +93 -0
- package/core/converters/index.ts +8 -0
- package/core/converters/managed-artifact.ts +119 -0
- package/core/converters/parser.ts +159 -0
- package/core/converters/template-renderer.ts +35 -0
- package/core/converters/writer.ts +61 -0
- package/core/dag-executor.ts +426 -0
- package/core/dag-loader.ts +292 -0
- package/core/dag-schemas.ts +150 -0
- package/core/dispatch.ts +125 -0
- package/core/evidence.ts +148 -0
- package/core/gate.ts +269 -0
- package/core/hook-engine.ts +566 -0
- package/core/host-probe.ts +64 -0
- package/core/implement.ts +16 -0
- package/core/isolation-errors.ts +174 -0
- package/core/isolation-resolver.ts +921 -0
- package/core/issue-context.ts +381 -0
- package/core/issue-readiness.ts +457 -0
- package/core/issue-sync.ts +427 -0
- package/core/issues.ts +132 -0
- package/core/land.ts +108 -0
- package/core/orchestrator.ts +54 -0
- package/core/phase-artifact.ts +32 -0
- package/core/phase-gates.ts +130 -0
- package/core/phase-rewind.ts +94 -0
- package/core/plan-lint.ts +61 -0
- package/core/plan.ts +77 -0
- package/core/port-allocation.ts +50 -0
- package/core/pr-check.ts +15 -0
- package/core/preset-system/preset-resolver.ts +221 -0
- package/core/project-init-status.ts +127 -0
- package/core/qa.ts +15 -0
- package/core/resilience.ts +165 -0
- package/core/runs.ts +288 -0
- package/core/safe-path.test.ts +80 -0
- package/core/safe-path.ts +60 -0
- package/core/sdd-gate.test.ts +98 -0
- package/core/sdd-gate.ts +134 -0
- package/core/self-review.ts +62 -0
- package/core/session.ts +70 -0
- package/core/ship.ts +86 -0
- package/core/specify.ts +173 -0
- package/core/state.ts +1002 -0
- package/core/template-engine.ts +152 -0
- package/core/template-resolver.test.ts +70 -0
- package/core/template-resolver.ts +156 -0
- package/core/triage.ts +26 -0
- package/core/verify.ts +15 -0
- package/core/wiki-native.ts +2423 -0
- package/core/wiki.ts +27 -0
- package/core/workflow-event-emitter.ts +163 -0
- package/core/workflows/engine.ts +273 -0
- package/core/workflows/expressions.ts +76 -0
- package/core/workflows/index.ts +38 -0
- package/core/workflows/steps/command.ts +43 -0
- package/core/workflows/steps/gate.ts +47 -0
- package/core/workflows/steps/gxpm.ts +44 -0
- package/core/workflows/steps/linear.ts +31 -0
- package/core/workflows/steps/shell.ts +65 -0
- package/core/workflows/types.ts +62 -0
- package/core/workspace-runtime.ts +227 -0
- package/core/worktree-init-steps.ts +647 -0
- package/core/worktree-init.ts +330 -0
- package/core/worktree-owner.ts +143 -0
- package/docs/GXPM_VERIFY.md +98 -0
- package/docs/INSTALL_FOR_AGENTS.md +113 -0
- package/docs/README.md +57 -0
- package/docs/adr/adr-005-multi-platform-skill-converter.md +72 -0
- package/docs/agents/domain.md +30 -0
- package/docs/agents/issue-tracker.md +30 -0
- package/docs/agents/triage-labels.md +32 -0
- package/docs/architecture/gxpm-architecture-diagram.md +265 -0
- package/docs/architecture/gxpm-current-architecture.md +175 -0
- package/docs/architecture/gxpm-current-flow.md +278 -0
- package/docs/architecture/gxpm-replacement-architecture.md +211 -0
- package/docs/architecture/gxpm-target-architecture.md +449 -0
- package/docs/architecture/gxpm-v0-contract.md +311 -0
- package/docs/architecture/layered-workflow-boundaries.md +193 -0
- package/docs/architecture/preset-system.md +126 -0
- package/docs/architecture/scaffold-northstar.md +23 -0
- package/docs/brainstorms/2026-05-14-bdd-then-tdd-design.md +320 -0
- package/docs/brainstorms/README.md +22 -0
- package/docs/brainstorms/docs-knowledge-system-requirements.md +29 -0
- package/docs/governance/beta-skill-promotion.md +39 -0
- package/docs/governance/development-contract.md +144 -0
- package/docs/governance/gherkin-style.md +90 -0
- package/docs/governance/host-adapter.md +56 -0
- package/docs/governance/skill-authoring.md +87 -0
- package/docs/governance/skill-testing.md +356 -0
- package/docs/governance/template-authoring.md +53 -0
- package/docs/migrations/v0.2.md +51 -0
- package/docs/plans/README.md +23 -0
- package/docs/plans/bdd-then-tdd-plan.md +1767 -0
- package/docs/plans/docs-knowledge-system-plan.md +31 -0
- package/docs/plans/spec-kit-sdd-adoption-plan.md +305 -0
- package/docs/research/agents-md-best-practices.md +207 -0
- package/docs/research/archon-study.md +351 -0
- package/docs/research/claude-hooks-study.md +440 -0
- package/docs/research/codex-hooks-study.md +624 -0
- package/docs/research/everything-claude-code-study.md +252 -0
- package/docs/research/from-skills-to-layered-workflow.md +322 -0
- package/docs/research/gsd-study.md +69 -0
- package/docs/research/kimi-hooks-study.md +274 -0
- package/docs/research/mattpocock-skills-comparison.md +429 -0
- package/docs/research/mattpocock-skills-study.md +275 -0
- package/docs/research/oh-my-codex-study.md +279 -0
- package/docs/research/perplexity-agent-skills-design.md +168 -0
- package/docs/research/pmc-gstack-skill-study.md +122 -0
- package/docs/research/spec-kit-study.md +224 -0
- package/docs/research/superpowers-study.md +209 -0
- package/docs/roadmap/initial-roadmap.md +53 -0
- package/docs/solutions/README.md +45 -0
- package/docs/solutions/artifact-nesting-recovery.md +58 -0
- package/docs/solutions/session-context-restore-practice.md +67 -0
- package/docs/solutions/workflow/version-drift-recovery.md +49 -0
- package/docs/solutions/worktree-gate-recovery.md +62 -0
- package/docs/specs/README.md +28 -0
- package/docs/specs/claude.md +45 -0
- package/docs/specs/codex.md +44 -0
- package/docs/specs/cursor.md +44 -0
- package/hosts/adapters/claude.ts +29 -0
- package/hosts/adapters/codex.ts +27 -0
- package/hosts/adapters/cursor.ts +27 -0
- package/hosts/adapters/kimi.ts +27 -0
- package/hosts/claude.ts +23 -0
- package/hosts/codex.ts +26 -0
- package/hosts/cursor.ts +19 -0
- package/hosts/index.ts +33 -0
- package/hosts/registry.test.ts +52 -0
- package/hosts/registry.ts +57 -0
- package/hosts/schema.ts +58 -0
- package/package.json +52 -0
- package/scripts/browser.ts +185 -0
- package/scripts/cleanup.ts +142 -0
- package/scripts/commands/artifact.ts +115 -0
- package/scripts/commands/autopilot.ts +143 -0
- package/scripts/commands/capability.ts +57 -0
- package/scripts/commands/config.ts +69 -0
- package/scripts/commands/dag.ts +126 -0
- package/scripts/commands/feedback.ts +123 -0
- package/scripts/commands/gate.ts +291 -0
- package/scripts/commands/helpers.ts +126 -0
- package/scripts/commands/hook.ts +66 -0
- package/scripts/commands/init.ts +515 -0
- package/scripts/commands/issue.ts +825 -0
- package/scripts/commands/phase.ts +61 -0
- package/scripts/commands/preset.ts +159 -0
- package/scripts/commands/runtime.ts +199 -0
- package/scripts/commands/specify.ts +71 -0
- package/scripts/commands/upgrade.ts +243 -0
- package/scripts/commands/verify.ts +183 -0
- package/scripts/commands/wiki.ts +242 -0
- package/scripts/commands/workflow.ts +131 -0
- package/scripts/dev-skill.ts +55 -0
- package/scripts/discover-skills.ts +116 -0
- package/scripts/doctor.ts +410 -0
- package/scripts/dogfood-check.ts +125 -0
- package/scripts/eval-functional.ts +218 -0
- package/scripts/eval.ts +246 -0
- package/scripts/gen-skill-docs.ts +201 -0
- package/scripts/global-discover.ts +217 -0
- package/scripts/governance-check.ts +75 -0
- package/scripts/gxpm-check.ts +12 -0
- package/scripts/gxpm.ts +216 -0
- package/scripts/host-config.ts +62 -0
- package/scripts/install-claude-hooks.ts +138 -0
- package/scripts/install-codex-hooks.ts +271 -0
- package/scripts/install-hooks.ts +128 -0
- package/scripts/install-kimi-hooks.ts +92 -0
- package/scripts/install-skill.ts +184 -0
- package/scripts/phase-artifact-commands.ts +100 -0
- package/scripts/post-land-sync.ts +46 -0
- package/scripts/scaffold-check.ts +85 -0
- package/scripts/skill-naming-check.ts +78 -0
- package/scripts/skill-structure-check.ts +157 -0
- package/scripts/skills-lock-check.ts +60 -0
- package/scripts/sync-markdown-artifacts.ts +172 -0
- package/scripts/uninstall.ts +162 -0
- package/scripts/version.ts +47 -0
- package/scripts/wait-pr-ready.ts +407 -0
- package/skills/gxpm/SKILL.md +485 -0
- package/skills/gxpm/SKILL.md.tmpl +422 -0
- package/skills/gxpm/references/CANON.md +53 -0
- package/skills/gxpm/references/key-rules.md +130 -0
- package/skills/gxpm-architecture/SKILL.md +106 -0
- package/skills/gxpm-architecture/references/DEEPENING.md +37 -0
- package/skills/gxpm-architecture/references/INTERFACE-DESIGN.md +44 -0
- package/skills/gxpm-autopilot/SKILL.md +116 -0
- package/skills/gxpm-autopilot/SKILL.md.tmpl +107 -0
- package/skills/gxpm-browser/SKILL.md +105 -0
- package/skills/gxpm-browser/SKILL.md.tmpl +41 -0
- package/skills/gxpm-browser/references/commands.md +43 -0
- package/skills/gxpm-browser/references/evidence-path.md +20 -0
- package/skills/gxpm-build/SKILL.md +78 -0
- package/skills/gxpm-cleanup/SKILL.md +76 -0
- package/skills/gxpm-debug-issue/SKILL.md +39 -0
- package/skills/gxpm-diagnose/SKILL.md +220 -0
- package/skills/gxpm-diagnose/SKILL.md.tmpl +31 -0
- package/skills/gxpm-diagnose/references/feedback-loop.md +34 -0
- package/skills/gxpm-diagnose/references/feedback-loops.md +43 -0
- package/skills/gxpm-diagnose/references/phases.md +60 -0
- package/skills/gxpm-eval/SKILL.md +78 -0
- package/skills/gxpm-explore-codebase/SKILL.md +36 -0
- package/skills/gxpm-explore-codebase/scripts/summarize-communities.ts +51 -0
- package/skills/gxpm-feedback/SKILL.md +122 -0
- package/skills/gxpm-grill/SKILL.md +159 -0
- package/skills/gxpm-grill/SKILL.md.tmpl +77 -0
- package/skills/gxpm-grill/references/documentation-templates.md +56 -0
- package/skills/gxpm-grill/references/process.md +25 -0
- package/skills/gxpm-handoff/SKILL.md +112 -0
- package/skills/gxpm-hygiene/SKILL.md +69 -0
- package/skills/gxpm-implementer/SKILL.md +142 -0
- package/skills/gxpm-implementer/SKILL.md.tmpl +141 -0
- package/skills/gxpm-linear/SKILL.md +282 -0
- package/skills/gxpm-linear/SKILL.md.tmpl +86 -0
- package/skills/gxpm-linear/references/commands.md +75 -0
- package/skills/gxpm-linear/references/workflows.md +120 -0
- package/skills/gxpm-planning/SKILL.md +134 -0
- package/skills/gxpm-prototype/SKILL.md +64 -0
- package/skills/gxpm-refactor-safely/SKILL.md +62 -0
- package/skills/gxpm-review-army/SKILL.md +117 -0
- package/skills/gxpm-review-changes/SKILL.md +36 -0
- package/skills/gxpm-setup/SKILL.md +101 -0
- package/skills/gxpm-specifier/SKILL.md +135 -0
- package/skills/gxpm-tdd/SKILL.md +187 -0
- package/skills/gxpm-tdd/references/interface-design.md +23 -0
- package/skills/gxpm-tdd/references/mocking.md +27 -0
- package/skills/gxpm-tdd/references/red-green-refactor.md +61 -0
- package/skills/gxpm-tdd/references/troubleshooting.md +28 -0
- package/skills/gxpm-tdd/references/workflow.md +50 -0
- package/skills/gxpm-tdd/testing-anti-patterns.tmpl +304 -0
- package/skills/gxpm-triage/SKILL.md +160 -0
- package/skills/gxpm-verify/SKILL.md +107 -0
- package/skills/gxpm-write-skill/SKILL.md +131 -0
- package/skills/gxpm-zoom-out/SKILL.md +69 -0
- package/skills/maintain-hygiene-skills-lock/SKILL.md +54 -0
- package/skills/maintain-hygiene-skills-lock/SKILL.md.tmpl +53 -0
- package/templates/constitution-template.md +63 -0
- package/templates/hooks/gxpm-commit-msg +16 -0
- package/templates/hooks/gxpm-post-checkout +19 -0
- package/templates/hooks/gxpm-post-commit +7 -0
- package/templates/hooks/gxpm-post-merge +29 -0
- package/templates/hooks/gxpm-pre-commit +39 -0
- package/templates/hooks/gxpm-pre-push +33 -0
- package/templates/plan-template.md.tmpl +46 -0
- package/templates/spec-template.md.tmpl +63 -0
- package/templates/specify-stub.tmpl +22 -0
- package/templates/tasks-template.md.tmpl +32 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { writeArtifact } from "./artifacts";
|
|
4
|
+
import { lintCodexPlans } from "./plan-lint";
|
|
5
|
+
import { readIssueState } from "./state";
|
|
6
|
+
import { AgentRegistry } from "./agent-runtime";
|
|
7
|
+
|
|
8
|
+
export function initializeSelfReview(input: { root?: string; issueId: string; army?: boolean }) {
|
|
9
|
+
const root = input.root ?? process.cwd();
|
|
10
|
+
const state = readIssueState({ root, issueId: input.issueId });
|
|
11
|
+
if (state.currentPhase !== "ac-check") {
|
|
12
|
+
throw new Error(`Self review can only be initialized from ac-check phase: current phase is ${state.currentPhase}`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const result = writeArtifact({
|
|
16
|
+
root,
|
|
17
|
+
issueId: input.issueId,
|
|
18
|
+
type: "self-review",
|
|
19
|
+
payload: {
|
|
20
|
+
findings: [],
|
|
21
|
+
reviewedArtifacts: ["acceptance-check", "local-verify"],
|
|
22
|
+
risks: [],
|
|
23
|
+
status: "draft",
|
|
24
|
+
summary: "",
|
|
25
|
+
plan_lint_findings: lintCodexPlans(root, input.issueId),
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// When army mode is enabled, also initialize the review-report artifact
|
|
30
|
+
if (input.army) {
|
|
31
|
+
const registry = new AgentRegistry();
|
|
32
|
+
// Discover from the project root (where agents/ directory lives)
|
|
33
|
+
// Fallback to process.cwd() if root doesn't contain agents/
|
|
34
|
+
const discoverRoot = existsSync(join(root, "agents")) ? root : process.cwd();
|
|
35
|
+
registry.discover(discoverRoot);
|
|
36
|
+
const agents = registry.listByArmy("review-army");
|
|
37
|
+
|
|
38
|
+
if (agents.length === 0) {
|
|
39
|
+
console.warn("warning: --army flag set but no review-army agents found");
|
|
40
|
+
} else {
|
|
41
|
+
// Write an initial review-report with empty findings
|
|
42
|
+
// The actual execution happens via the host's subagent mechanism
|
|
43
|
+
writeArtifact({
|
|
44
|
+
root,
|
|
45
|
+
issueId: input.issueId,
|
|
46
|
+
type: "review-report",
|
|
47
|
+
payload: {
|
|
48
|
+
army: "review-army",
|
|
49
|
+
phase: "self-review",
|
|
50
|
+
issueId: input.issueId,
|
|
51
|
+
generatedAt: new Date().toISOString(),
|
|
52
|
+
findings: [],
|
|
53
|
+
summary: `${agents.length} review role(s) queued for execution: ${agents.map((a) => a.name).join(", ")}`,
|
|
54
|
+
status: "draft",
|
|
55
|
+
agents: agents.map((a) => ({ name: a.name, role: a.role, description: a.description })),
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return result;
|
|
62
|
+
}
|
package/core/session.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir, tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { getConfigValue } from "./config";
|
|
6
|
+
|
|
7
|
+
let volatileSessionId: string | null = null;
|
|
8
|
+
|
|
9
|
+
export function resolveSessionId(env: NodeJS.ProcessEnv = process.env): string {
|
|
10
|
+
const codexSessionId = env.CODEX_COMPANION_SESSION_ID?.trim();
|
|
11
|
+
if (codexSessionId) {
|
|
12
|
+
return `codex:${codexSessionId}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const cmuxSurfaceId = env.CMUX_SURFACE_ID?.trim();
|
|
16
|
+
if (cmuxSurfaceId) {
|
|
17
|
+
return `cmux:${cmuxSurfaceId}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return `gen:${readOrCreateGeneratedSessionId(env)}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface AgentIdentity {
|
|
24
|
+
host: string;
|
|
25
|
+
sessionId: string;
|
|
26
|
+
actor: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function resolveAgentIdentity(
|
|
30
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
31
|
+
root?: string,
|
|
32
|
+
): AgentIdentity {
|
|
33
|
+
const sessionId = resolveSessionId(env);
|
|
34
|
+
const host = sessionId.split(":")[0] ?? "gen";
|
|
35
|
+
const envName = env.GXPM_AGENT_NAME?.trim() ?? "";
|
|
36
|
+
const configName = root ? String(getConfigValue({ root, key: "agent.name" }).value ?? "").trim() : "";
|
|
37
|
+
const actor = envName || configName || host;
|
|
38
|
+
return { host, sessionId, actor };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function readOrCreateGeneratedSessionId(env: NodeJS.ProcessEnv): string {
|
|
42
|
+
for (const cachePath of candidateCachePaths(env)) {
|
|
43
|
+
try {
|
|
44
|
+
if (existsSync(cachePath)) {
|
|
45
|
+
const cached = readFileSync(cachePath, "utf8").trim();
|
|
46
|
+
if (cached) {
|
|
47
|
+
return cached;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const generated = randomUUID();
|
|
52
|
+
mkdirSync(join(cachePath, ".."), { recursive: true });
|
|
53
|
+
writeFileSync(cachePath, `${generated}\n`);
|
|
54
|
+
return generated;
|
|
55
|
+
} catch {}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
volatileSessionId ??= randomUUID();
|
|
59
|
+
return volatileSessionId;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function candidateCachePaths(env: NodeJS.ProcessEnv): string[] {
|
|
63
|
+
const paths = new Set<string>();
|
|
64
|
+
const home = env.HOME?.trim() || homedir();
|
|
65
|
+
if (home) {
|
|
66
|
+
paths.add(join(home, ".gxpm", "session-id"));
|
|
67
|
+
}
|
|
68
|
+
paths.add(join(tmpdir(), "gxpm", "session-id"));
|
|
69
|
+
return [...paths];
|
|
70
|
+
}
|
package/core/ship.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { writeArtifact } from "./artifacts";
|
|
4
|
+
import { readIssueState } from "./state";
|
|
5
|
+
import { AgentRegistry } from "./agent-runtime";
|
|
6
|
+
|
|
7
|
+
export function initializeShipReadiness(input: { root?: string; issueId: string; army?: boolean }) {
|
|
8
|
+
const root = input.root ?? process.cwd();
|
|
9
|
+
const state = readIssueState({ root, issueId: input.issueId });
|
|
10
|
+
if (state.currentPhase !== "cleanup" && state.currentPhase !== "self-review") {
|
|
11
|
+
throw new Error(`Ship readiness can only be initialized from cleanup or self-review phase: current phase is ${state.currentPhase}`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const result = writeArtifact({
|
|
15
|
+
root,
|
|
16
|
+
issueId: input.issueId,
|
|
17
|
+
type: "ship-readiness",
|
|
18
|
+
payload: {
|
|
19
|
+
checklist: [],
|
|
20
|
+
compatibilityMigration: {
|
|
21
|
+
backwardCompatible: true,
|
|
22
|
+
configChanges: false,
|
|
23
|
+
migrationSteps: "",
|
|
24
|
+
},
|
|
25
|
+
blastRadius: {
|
|
26
|
+
affectedSubsystems: [],
|
|
27
|
+
guardrails: "",
|
|
28
|
+
unintendedEffects: "",
|
|
29
|
+
},
|
|
30
|
+
humanVerification: {
|
|
31
|
+
edgeCases: "",
|
|
32
|
+
notVerified: "",
|
|
33
|
+
verifiedScenarios: "",
|
|
34
|
+
},
|
|
35
|
+
releaseNotes: "",
|
|
36
|
+
reviewedArtifacts: ["self-review", "acceptance-check"],
|
|
37
|
+
risks: [],
|
|
38
|
+
risksAndMitigations: [],
|
|
39
|
+
rollbackPlan: {
|
|
40
|
+
failureSymptoms: "",
|
|
41
|
+
featureFlags: "",
|
|
42
|
+
rollbackCommand: "",
|
|
43
|
+
},
|
|
44
|
+
securityImpact: {
|
|
45
|
+
fileSystemAccessChanged: false,
|
|
46
|
+
networkCallsChanged: false,
|
|
47
|
+
newPermissionsOrCapabilities: false,
|
|
48
|
+
riskAndMitigation: "",
|
|
49
|
+
secretsHandlingChanged: false,
|
|
50
|
+
},
|
|
51
|
+
status: "draft",
|
|
52
|
+
summary: "",
|
|
53
|
+
targetBranch: "",
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// When army mode is enabled, also initialize the ship-audit-report artifact
|
|
58
|
+
if (input.army) {
|
|
59
|
+
const registry = new AgentRegistry();
|
|
60
|
+
const discoverRoot = existsSync(join(root, "agents")) ? root : process.cwd();
|
|
61
|
+
registry.discover(discoverRoot);
|
|
62
|
+
const agents = registry.listByArmy("ship-audit-army");
|
|
63
|
+
|
|
64
|
+
if (agents.length === 0) {
|
|
65
|
+
console.warn("warning: --army flag set but no ship-audit-army agents found");
|
|
66
|
+
} else {
|
|
67
|
+
writeArtifact({
|
|
68
|
+
root,
|
|
69
|
+
issueId: input.issueId,
|
|
70
|
+
type: "ship-audit-report",
|
|
71
|
+
payload: {
|
|
72
|
+
army: "ship-audit-army",
|
|
73
|
+
phase: "ship",
|
|
74
|
+
issueId: input.issueId,
|
|
75
|
+
generatedAt: new Date().toISOString(),
|
|
76
|
+
findings: [],
|
|
77
|
+
summary: `${agents.length} audit role(s) queued for execution: ${agents.map((a) => a.name).join(", ")}`,
|
|
78
|
+
status: "draft",
|
|
79
|
+
agents: agents.map((a) => ({ name: a.name, role: a.role, description: a.description })),
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return result;
|
|
86
|
+
}
|
package/core/specify.ts
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import { writeArtifact } from "./artifacts";
|
|
5
|
+
import { readIssueState } from "./state";
|
|
6
|
+
import { resolveAgentIdentity } from "./session";
|
|
7
|
+
import { BehaviorSpecSchema, type BehaviorSpec } from "./contracts/behavior-spec.schema";
|
|
8
|
+
|
|
9
|
+
interface SpecifyInput {
|
|
10
|
+
root?: string;
|
|
11
|
+
issueId: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function initializeSpecify(input: SpecifyInput) {
|
|
15
|
+
const state = readIssueState({ root: input.root, issueId: input.issueId });
|
|
16
|
+
if (state.currentPhase !== "specify") {
|
|
17
|
+
throw new Error(
|
|
18
|
+
`Specify can only be initialized from specify phase: current phase is ${state.currentPhase}`,
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const now = new Date().toISOString();
|
|
23
|
+
const identity = resolveAgentIdentity();
|
|
24
|
+
|
|
25
|
+
const payload = {
|
|
26
|
+
$schema: "behavior-spec.v1",
|
|
27
|
+
issueId: input.issueId,
|
|
28
|
+
createdAt: now,
|
|
29
|
+
createdBy: identity.actor,
|
|
30
|
+
confirmedAt: null,
|
|
31
|
+
confirmedBy: null,
|
|
32
|
+
feature: {
|
|
33
|
+
title: "<placeholder>",
|
|
34
|
+
asA: "<placeholder>",
|
|
35
|
+
iWant: "<placeholder>",
|
|
36
|
+
soThat: "<placeholder>",
|
|
37
|
+
},
|
|
38
|
+
scenarios: [
|
|
39
|
+
{
|
|
40
|
+
id: "scn-01",
|
|
41
|
+
name: "<placeholder>",
|
|
42
|
+
given: ["<placeholder>"],
|
|
43
|
+
when: "<placeholder>",
|
|
44
|
+
then: ["<placeholder>"],
|
|
45
|
+
examples: [],
|
|
46
|
+
stubPath: "<placeholder>",
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
guidelinesRef: "docs/governance/gherkin-style.md@v1",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return writeArtifact({
|
|
53
|
+
root: input.root,
|
|
54
|
+
issueId: input.issueId,
|
|
55
|
+
type: "behavior-spec",
|
|
56
|
+
payload,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Helpers
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
interface ConfirmInput {
|
|
65
|
+
root?: string;
|
|
66
|
+
issueId: string;
|
|
67
|
+
confirmedBy?: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function gitUserEmail(): string {
|
|
71
|
+
try {
|
|
72
|
+
return execSync("git config user.email", { encoding: "utf8" }).trim();
|
|
73
|
+
} catch {
|
|
74
|
+
return "unknown@local";
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function specArtifactPath(root: string, issueId: string): string {
|
|
79
|
+
return join(root, ".gxpm", "issues", issueId, "artifacts", "behavior-spec.json");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function readStoredSpec(root: string, issueId: string): { stored: { payload: BehaviorSpec; [key: string]: unknown } } {
|
|
83
|
+
const path = specArtifactPath(root, issueId);
|
|
84
|
+
if (!existsSync(path)) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`behavior-spec.json not found for ${issueId}; run \`gxpm specify init ${issueId}\` first`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
const stored = JSON.parse(readFileSync(path, "utf8"));
|
|
90
|
+
BehaviorSpecSchema.parse(stored.payload);
|
|
91
|
+
return { stored };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const PLACEHOLDER_SENTINEL = "<placeholder>";
|
|
95
|
+
|
|
96
|
+
function findRemainingPlaceholders(spec: BehaviorSpec): string[] {
|
|
97
|
+
const violations: string[] = [];
|
|
98
|
+
const f = spec.feature;
|
|
99
|
+
for (const [key, val] of Object.entries(f)) {
|
|
100
|
+
if (val === PLACEHOLDER_SENTINEL) violations.push(`feature.${key}`);
|
|
101
|
+
}
|
|
102
|
+
spec.scenarios.forEach((scn, idx) => {
|
|
103
|
+
const tag = `scenarios[${idx}]`;
|
|
104
|
+
if (scn.name === PLACEHOLDER_SENTINEL) violations.push(`${tag}.name`);
|
|
105
|
+
if (scn.when === PLACEHOLDER_SENTINEL) violations.push(`${tag}.when`);
|
|
106
|
+
if (scn.stubPath === PLACEHOLDER_SENTINEL) violations.push(`${tag}.stubPath`);
|
|
107
|
+
scn.given.forEach((g, i) => {
|
|
108
|
+
if (g === PLACEHOLDER_SENTINEL) violations.push(`${tag}.given[${i}]`);
|
|
109
|
+
});
|
|
110
|
+
scn.then.forEach((t, i) => {
|
|
111
|
+
if (t === PLACEHOLDER_SENTINEL) violations.push(`${tag}.then[${i}]`);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
return violations;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
// Exported functions
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
|
|
121
|
+
export function confirmSpecify(input: ConfirmInput) {
|
|
122
|
+
const root = input.root ?? process.cwd();
|
|
123
|
+
const { stored } = readStoredSpec(root, input.issueId);
|
|
124
|
+
const spec = stored.payload;
|
|
125
|
+
|
|
126
|
+
const placeholders = findRemainingPlaceholders(spec);
|
|
127
|
+
if (placeholders.length > 0) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`Cannot confirm: <placeholder> sentinels remain in: ${placeholders.join(", ")}`,
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
for (const scn of spec.scenarios) {
|
|
134
|
+
const file = scn.stubPath.split(":")[0];
|
|
135
|
+
const abs = join(root, file);
|
|
136
|
+
if (!existsSync(abs)) {
|
|
137
|
+
throw new Error(`Stub file missing: ${file} (scenario ${scn.id})`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const now = new Date().toISOString();
|
|
142
|
+
const confirmedBy = input.confirmedBy ?? gitUserEmail();
|
|
143
|
+
stored.payload.confirmedAt = now;
|
|
144
|
+
stored.payload.confirmedBy = confirmedBy;
|
|
145
|
+
writeFileSync(
|
|
146
|
+
specArtifactPath(root, input.issueId),
|
|
147
|
+
`${JSON.stringify(stored, null, 2)}\n`,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function reviseSpecify(input: { root?: string; issueId: string }) {
|
|
152
|
+
const root = input.root ?? process.cwd();
|
|
153
|
+
// Revise must be schema-tolerant: its purpose is to restore a confirmable state.
|
|
154
|
+
// We deliberately skip BehaviorSpecSchema.parse() so a spec that was edited into
|
|
155
|
+
// an inconsistent shape (e.g. confirmedAt set but confirmedBy null, or partial
|
|
156
|
+
// field types) can still be reset to draft. The next confirmSpecify call will
|
|
157
|
+
// re-validate against the full schema.
|
|
158
|
+
const path = specArtifactPath(root, input.issueId);
|
|
159
|
+
if (!existsSync(path)) {
|
|
160
|
+
throw new Error(
|
|
161
|
+
`behavior-spec.json not found for ${input.issueId}; run \`gxpm specify init ${input.issueId}\` first`,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
const stored = JSON.parse(readFileSync(path, "utf8"));
|
|
165
|
+
if (!stored?.payload || typeof stored.payload !== "object") {
|
|
166
|
+
throw new Error(
|
|
167
|
+
`behavior-spec.json is malformed (missing payload) for ${input.issueId}; cannot revise`,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
stored.payload.confirmedAt = null;
|
|
171
|
+
stored.payload.confirmedBy = null;
|
|
172
|
+
writeFileSync(path, `${JSON.stringify(stored, null, 2)}\n`);
|
|
173
|
+
}
|