@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,54 @@
|
|
|
1
|
+
import { listIssues } from "./issues";
|
|
2
|
+
import { classifyIssueReadiness } from "./issue-readiness";
|
|
3
|
+
import { type GxpmPhase, type IssueType } from "./state";
|
|
4
|
+
|
|
5
|
+
export type DryRunDecision = "dispatchable" | "blocked" | "ignored";
|
|
6
|
+
|
|
7
|
+
export interface OrchestratorDryRunIssue {
|
|
8
|
+
issueId: string;
|
|
9
|
+
issueType: IssueType;
|
|
10
|
+
currentPhase: GxpmPhase;
|
|
11
|
+
decision: DryRunDecision;
|
|
12
|
+
reason: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface OrchestratorDryRunReport {
|
|
16
|
+
schemaVersion: 1;
|
|
17
|
+
generatedAt: string;
|
|
18
|
+
issues: OrchestratorDryRunIssue[];
|
|
19
|
+
summary: {
|
|
20
|
+
dispatchable: number;
|
|
21
|
+
blocked: number;
|
|
22
|
+
ignored: number;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function dryRunOrchestratorTick(input: { root?: string; includeAll?: boolean } = {}): OrchestratorDryRunReport {
|
|
27
|
+
const root = input.root ?? process.cwd();
|
|
28
|
+
const entries = listIssues({ root, includeAll: true });
|
|
29
|
+
const issues = entries
|
|
30
|
+
.map((entry) => classifyIssue(root, entry.issueId))
|
|
31
|
+
.filter((issue) => input.includeAll || issue.decision !== "ignored");
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
schemaVersion: 1,
|
|
35
|
+
generatedAt: new Date().toISOString(),
|
|
36
|
+
issues,
|
|
37
|
+
summary: {
|
|
38
|
+
dispatchable: issues.filter((issue) => issue.decision === "dispatchable").length,
|
|
39
|
+
blocked: issues.filter((issue) => issue.decision === "blocked").length,
|
|
40
|
+
ignored: issues.filter((issue) => issue.decision === "ignored").length,
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function classifyIssue(root: string, issueId: string): OrchestratorDryRunIssue {
|
|
46
|
+
const readiness = classifyIssueReadiness({ root, issueId });
|
|
47
|
+
return {
|
|
48
|
+
issueId: readiness.issueId,
|
|
49
|
+
issueType: readiness.issueType,
|
|
50
|
+
currentPhase: readiness.currentPhase,
|
|
51
|
+
decision: readiness.decision === "ready" ? "dispatchable" : readiness.decision,
|
|
52
|
+
reason: readiness.reason,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type ArtifactType, writeArtifact } from "./artifacts";
|
|
2
|
+
import { readIssueState, type GxpmPhase } from "./state";
|
|
3
|
+
|
|
4
|
+
export interface PhaseArtifactInput {
|
|
5
|
+
root?: string;
|
|
6
|
+
issueId: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface PhaseArtifactInitializerConfig {
|
|
10
|
+
artifactType: ArtifactType;
|
|
11
|
+
label: string;
|
|
12
|
+
payload: unknown;
|
|
13
|
+
requiredPhase: GxpmPhase;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function createPhaseArtifactInitializer(config: PhaseArtifactInitializerConfig) {
|
|
17
|
+
return function initializePhaseArtifact(input: PhaseArtifactInput) {
|
|
18
|
+
const state = readIssueState({ root: input.root, issueId: input.issueId });
|
|
19
|
+
if (state.currentPhase !== config.requiredPhase) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
`${config.label} can only be initialized from ${config.requiredPhase} phase: current phase is ${state.currentPhase}`,
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return writeArtifact({
|
|
26
|
+
root: input.root,
|
|
27
|
+
issueId: input.issueId,
|
|
28
|
+
type: config.artifactType,
|
|
29
|
+
payload: config.payload,
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { type ArtifactType } from "./artifacts";
|
|
2
|
+
import { type GxpmPhase } from "./state";
|
|
3
|
+
|
|
4
|
+
export const CODE_COMMIT_PHASES: ReadonlySet<GxpmPhase> = new Set([
|
|
5
|
+
"dispatch",
|
|
6
|
+
"implement",
|
|
7
|
+
"local-verify",
|
|
8
|
+
"ac-check",
|
|
9
|
+
"self-review",
|
|
10
|
+
"cleanup",
|
|
11
|
+
"ship",
|
|
12
|
+
"pr-check",
|
|
13
|
+
"verify",
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
export const PROTECTED_PATH_PATTERNS: readonly RegExp[] = [
|
|
17
|
+
/^apps\//,
|
|
18
|
+
/^server\//,
|
|
19
|
+
/^packages\//,
|
|
20
|
+
/^scripts\//,
|
|
21
|
+
/^tests\//,
|
|
22
|
+
/^supabase\//,
|
|
23
|
+
/^e2e\//,
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
export interface PhaseGateRule {
|
|
27
|
+
command: string;
|
|
28
|
+
fromPhase: GxpmPhase;
|
|
29
|
+
nextPhase: GxpmPhase;
|
|
30
|
+
requiredArtifact: ArtifactType;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const PHASE_GATE_RULES: PhaseGateRule[] = [
|
|
34
|
+
{
|
|
35
|
+
command: "gxpm triage init <issue-id>",
|
|
36
|
+
fromPhase: "triage",
|
|
37
|
+
nextPhase: "plan",
|
|
38
|
+
requiredArtifact: "acceptance-contract",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
command: "gxpm plan init <issue-id>",
|
|
42
|
+
fromPhase: "plan",
|
|
43
|
+
nextPhase: "dispatch",
|
|
44
|
+
requiredArtifact: "implementation-plan",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
command: "gxpm dispatch init <issue-id>",
|
|
48
|
+
fromPhase: "dispatch",
|
|
49
|
+
nextPhase: "specify",
|
|
50
|
+
requiredArtifact: "dispatch-handoff",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
command: "gxpm specify init <issue-id>",
|
|
54
|
+
fromPhase: "specify",
|
|
55
|
+
nextPhase: "implement",
|
|
56
|
+
requiredArtifact: "behavior-spec",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
command: "gxpm implement verify <issue-id>",
|
|
60
|
+
fromPhase: "implement",
|
|
61
|
+
nextPhase: "local-verify",
|
|
62
|
+
requiredArtifact: "local-verify",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
command: "gxpm local-verify ac-check <issue-id>",
|
|
66
|
+
fromPhase: "local-verify",
|
|
67
|
+
nextPhase: "ac-check",
|
|
68
|
+
requiredArtifact: "acceptance-check",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
command: "gxpm ac-check self-review <issue-id>",
|
|
72
|
+
fromPhase: "ac-check",
|
|
73
|
+
nextPhase: "self-review",
|
|
74
|
+
requiredArtifact: "self-review",
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
command: "gxpm self-review cleanup <issue-id>",
|
|
78
|
+
fromPhase: "self-review",
|
|
79
|
+
nextPhase: "cleanup",
|
|
80
|
+
requiredArtifact: "cleanup-report",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
command: "gxpm cleanup ship <issue-id>",
|
|
84
|
+
fromPhase: "cleanup",
|
|
85
|
+
nextPhase: "ship",
|
|
86
|
+
requiredArtifact: "ship-readiness",
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
command: "gxpm ship pr-check <issue-id>",
|
|
90
|
+
fromPhase: "ship",
|
|
91
|
+
nextPhase: "pr-check",
|
|
92
|
+
requiredArtifact: "pr-check",
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
command: "gxpm pr-check verify <issue-id>",
|
|
96
|
+
fromPhase: "pr-check",
|
|
97
|
+
nextPhase: "verify",
|
|
98
|
+
requiredArtifact: "verify-findings",
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
command: "gxpm verify qa <issue-id>",
|
|
102
|
+
fromPhase: "verify",
|
|
103
|
+
nextPhase: "qa",
|
|
104
|
+
requiredArtifact: "qa-findings",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
command: "gxpm qa land <issue-id>",
|
|
108
|
+
fromPhase: "qa",
|
|
109
|
+
nextPhase: "land",
|
|
110
|
+
requiredArtifact: "land-findings",
|
|
111
|
+
},
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
export const GATE_ARTIFACT_TYPES = PHASE_GATE_RULES.map((rule) => rule.requiredArtifact);
|
|
115
|
+
|
|
116
|
+
export function isGateArtifact(artifactType: string): artifactType is ArtifactType {
|
|
117
|
+
return GATE_ARTIFACT_TYPES.includes(artifactType as ArtifactType);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function getRequiredArtifactForTransition(fromPhase: GxpmPhase, nextPhase: GxpmPhase) {
|
|
121
|
+
return (
|
|
122
|
+
PHASE_GATE_RULES.find((rule) => rule.fromPhase === fromPhase && rule.nextPhase === nextPhase)
|
|
123
|
+
?.requiredArtifact ?? null
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function getGateCommand(issueId: string, artifactType: string) {
|
|
128
|
+
const rule = PHASE_GATE_RULES.find((item) => item.requiredArtifact === artifactType);
|
|
129
|
+
return (rule?.command ?? "gxpm triage init <issue-id>").replace("<issue-id>", issueId);
|
|
130
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import {
|
|
3
|
+
appendIssueEvent,
|
|
4
|
+
assertValidPhase,
|
|
5
|
+
getIssuePaths,
|
|
6
|
+
readIssueState,
|
|
7
|
+
type GxpmPhase,
|
|
8
|
+
} from "./state";
|
|
9
|
+
import { resolveSessionId } from "./session";
|
|
10
|
+
|
|
11
|
+
export interface RewindInput {
|
|
12
|
+
root?: string;
|
|
13
|
+
issueId: string;
|
|
14
|
+
toPhase: GxpmPhase | string;
|
|
15
|
+
reason: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface RewindResult {
|
|
19
|
+
fromPhase: GxpmPhase;
|
|
20
|
+
toPhase: GxpmPhase;
|
|
21
|
+
timestamp: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function rewindPhase(input: RewindInput): RewindResult {
|
|
25
|
+
const toPhase = assertValidPhase(input.toPhase);
|
|
26
|
+
if (!input.reason || !input.reason.trim()) {
|
|
27
|
+
throw new Error("phase rewind requires --reason; explain why the rewind is needed");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const root = input.root ?? process.cwd();
|
|
31
|
+
const paths = getIssuePaths(root, input.issueId);
|
|
32
|
+
const state = readIssueState({ root, issueId: input.issueId });
|
|
33
|
+
|
|
34
|
+
if (state.currentPhase === toPhase) {
|
|
35
|
+
throw new Error(`Issue ${input.issueId} is already in phase ${toPhase}; nothing to rewind`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const everEntered = state.phaseHistory?.some((h) => h.phase === toPhase);
|
|
39
|
+
if (!everEntered) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`Cannot rewind ${input.issueId} to ${toPhase}: phaseHistory does not contain that phase. ` +
|
|
42
|
+
`Only previously-entered phases may be rewound to.`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const now = new Date().toISOString();
|
|
47
|
+
const fromPhase = state.currentPhase;
|
|
48
|
+
|
|
49
|
+
const updated = {
|
|
50
|
+
...state,
|
|
51
|
+
currentPhase: toPhase,
|
|
52
|
+
updatedAt: now,
|
|
53
|
+
phaseHistory: [
|
|
54
|
+
...state.phaseHistory,
|
|
55
|
+
{ phase: toPhase, enteredAt: now, fromPhase },
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
writeFileSync(paths.statePath, `${JSON.stringify(updated, null, 2)}\n`);
|
|
59
|
+
|
|
60
|
+
const graph = JSON.parse(readFileSync(paths.graphPath, "utf8"));
|
|
61
|
+
writeFileSync(
|
|
62
|
+
paths.graphPath,
|
|
63
|
+
`${JSON.stringify(
|
|
64
|
+
{
|
|
65
|
+
...graph,
|
|
66
|
+
currentPhase: toPhase,
|
|
67
|
+
transitions: [
|
|
68
|
+
...(Array.isArray(graph.transitions) ? graph.transitions : []),
|
|
69
|
+
{ fromPhase, toPhase, timestamp: now, rewound: true },
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
null,
|
|
73
|
+
2,
|
|
74
|
+
)}\n`,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
appendIssueEvent({
|
|
78
|
+
issueDir: paths.issueDir,
|
|
79
|
+
event: {
|
|
80
|
+
schemaVersion: 1,
|
|
81
|
+
type: "phase.rewound",
|
|
82
|
+
issueId: input.issueId,
|
|
83
|
+
timestamp: now,
|
|
84
|
+
sessionId: resolveSessionId(),
|
|
85
|
+
payload: {
|
|
86
|
+
fromPhase,
|
|
87
|
+
toPhase,
|
|
88
|
+
reason: input.reason.trim(),
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
return { fromPhase, toPhase, timestamp: now };
|
|
94
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
const PHASE_KEYWORDS = [
|
|
5
|
+
"triage",
|
|
6
|
+
"plan",
|
|
7
|
+
"dispatch",
|
|
8
|
+
"implement",
|
|
9
|
+
"local-verify",
|
|
10
|
+
"ac-check",
|
|
11
|
+
"self-review",
|
|
12
|
+
"ship",
|
|
13
|
+
"pr-check",
|
|
14
|
+
"verify",
|
|
15
|
+
"qa",
|
|
16
|
+
"land",
|
|
17
|
+
];
|
|
18
|
+
const TRANSITION_KEYWORDS = ["transition", "推进到", "next phase", "phase"];
|
|
19
|
+
|
|
20
|
+
export interface PlanLintItem {
|
|
21
|
+
text: string;
|
|
22
|
+
matchedKeywords: string[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface PlanLintFindings {
|
|
26
|
+
hasFindings: boolean;
|
|
27
|
+
items: PlanLintItem[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function lintCodexPlans(root: string, issueId: string): PlanLintFindings {
|
|
31
|
+
const path = join(root, ".gxpm", "issues", issueId, "codex-plans.jsonl");
|
|
32
|
+
if (!existsSync(path)) {
|
|
33
|
+
return { hasFindings: false, items: [] };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const items = readFileSync(path, "utf8")
|
|
37
|
+
.split("\n")
|
|
38
|
+
.map((line) => line.trim())
|
|
39
|
+
.filter(Boolean)
|
|
40
|
+
.flatMap((line) => extractPlanSteps(JSON.parse(line)));
|
|
41
|
+
|
|
42
|
+
const findings = items.flatMap((text) => {
|
|
43
|
+
const matched = [
|
|
44
|
+
...PHASE_KEYWORDS.filter((keyword) => text.includes(keyword)),
|
|
45
|
+
...TRANSITION_KEYWORDS.filter((keyword) => text.includes(keyword)),
|
|
46
|
+
];
|
|
47
|
+
const hasPhase = PHASE_KEYWORDS.some((keyword) => text.includes(keyword));
|
|
48
|
+
const hasTransition = TRANSITION_KEYWORDS.some((keyword) => text.includes(keyword));
|
|
49
|
+
return hasPhase && hasTransition ? [{ text, matchedKeywords: matched }] : [];
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return { hasFindings: findings.length > 0, items: findings };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function extractPlanSteps(entry: any): string[] {
|
|
56
|
+
const steps = entry?.arguments?.steps;
|
|
57
|
+
if (!Array.isArray(steps)) return [];
|
|
58
|
+
return steps
|
|
59
|
+
.map((step) => step?.description)
|
|
60
|
+
.filter((value): value is string => typeof value === "string");
|
|
61
|
+
}
|
package/core/plan.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { writeArtifact } from "./artifacts";
|
|
2
|
+
import { readIssueState } from "./state";
|
|
3
|
+
|
|
4
|
+
interface PlanInput {
|
|
5
|
+
root?: string;
|
|
6
|
+
issueId: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function initializePlan(input: PlanInput) {
|
|
10
|
+
const state = readIssueState({ root: input.root, issueId: input.issueId });
|
|
11
|
+
if (state.currentPhase !== "plan") {
|
|
12
|
+
throw new Error(
|
|
13
|
+
`Plan can only be initialized from plan phase: current phase is ${state.currentPhase}`,
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const rigor = state.rigorLevel ?? "standard";
|
|
18
|
+
|
|
19
|
+
const constitutionCheck = {
|
|
20
|
+
capabilityDeclared: false,
|
|
21
|
+
testStrategyDefined: false,
|
|
22
|
+
simplicityJustified: false,
|
|
23
|
+
integrationPathClear: false,
|
|
24
|
+
status: "pending",
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const litePayload = {
|
|
28
|
+
objective: "",
|
|
29
|
+
scope: "",
|
|
30
|
+
nonGoals: "",
|
|
31
|
+
constitutionCheck,
|
|
32
|
+
steps: [],
|
|
33
|
+
status: "draft",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const standardPayload = {
|
|
37
|
+
objective: "",
|
|
38
|
+
scope: "",
|
|
39
|
+
nonGoals: "",
|
|
40
|
+
approach: "",
|
|
41
|
+
constitutionCheck,
|
|
42
|
+
steps: [],
|
|
43
|
+
risks: [],
|
|
44
|
+
validation: [],
|
|
45
|
+
rollback: "",
|
|
46
|
+
status: "draft",
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const fullPayload = {
|
|
50
|
+
objective: "",
|
|
51
|
+
scope: "",
|
|
52
|
+
nonGoals: "",
|
|
53
|
+
approach: "",
|
|
54
|
+
chosenApproach: "",
|
|
55
|
+
whyThisApproach: "",
|
|
56
|
+
implementationSlices: [],
|
|
57
|
+
dataModel: "",
|
|
58
|
+
alternativesConsidered: [],
|
|
59
|
+
alternativesRejected: [],
|
|
60
|
+
constitutionCheck,
|
|
61
|
+
steps: [],
|
|
62
|
+
risks: [],
|
|
63
|
+
validation: [],
|
|
64
|
+
rollback: "",
|
|
65
|
+
migrationPlan: "",
|
|
66
|
+
status: "draft",
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const payload = rigor === "lite" ? litePayload : rigor === "full" ? fullPayload : standardPayload;
|
|
70
|
+
|
|
71
|
+
return writeArtifact({
|
|
72
|
+
root: input.root,
|
|
73
|
+
issueId: input.issueId,
|
|
74
|
+
type: "implementation-plan",
|
|
75
|
+
payload,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { getResolvedConfigValue } from "./config";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Calculate a deterministic port offset from a path string.
|
|
6
|
+
* Uses MD5 hash to produce an offset in range 100-999.
|
|
7
|
+
* Same path always yields same offset.
|
|
8
|
+
*/
|
|
9
|
+
export function calculatePortOffset(path: string): number {
|
|
10
|
+
const hash = createHash("md5").update(path).digest();
|
|
11
|
+
return (hash.readUInt16BE(0) % 900) + 100;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ResolveDevPortInput {
|
|
15
|
+
/** Workspace path used as hash seed. */
|
|
16
|
+
workspacePath: string;
|
|
17
|
+
/** Optional explicit base port (defaults to config or 3090). */
|
|
18
|
+
basePort?: number;
|
|
19
|
+
/** Optional root for config resolution. */
|
|
20
|
+
root?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the dev server port for a workspace.
|
|
25
|
+
*
|
|
26
|
+
* Precedence:
|
|
27
|
+
* 1. GXPM_DEV_PORT env var (validated; exits on invalid)
|
|
28
|
+
* 2. PORT env var (validated; exits on invalid)
|
|
29
|
+
* 3. basePort + calculatePortOffset(workspacePath)
|
|
30
|
+
*
|
|
31
|
+
* @returns The resolved port number
|
|
32
|
+
*/
|
|
33
|
+
export function resolveDevPort(input: ResolveDevPortInput): number {
|
|
34
|
+
const envPort = process.env.GXPM_DEV_PORT ?? process.env.PORT;
|
|
35
|
+
|
|
36
|
+
if (envPort) {
|
|
37
|
+
const parsed = Number(envPort);
|
|
38
|
+
if (!Number.isInteger(parsed) || parsed < 1 || parsed > 65535) {
|
|
39
|
+
throw new Error(`Invalid port env var: ${envPort} (must be 1-65535)`);
|
|
40
|
+
}
|
|
41
|
+
return parsed;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const basePort =
|
|
45
|
+
input.basePort ??
|
|
46
|
+
Number(getResolvedConfigValue({ root: input.root, key: "workspace.basePort" }).value);
|
|
47
|
+
|
|
48
|
+
const offset = calculatePortOffset(input.workspacePath);
|
|
49
|
+
return basePort + offset;
|
|
50
|
+
}
|
package/core/pr-check.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createPhaseArtifactInitializer } from "./phase-artifact";
|
|
2
|
+
|
|
3
|
+
export const initializePrCheck = createPhaseArtifactInitializer({
|
|
4
|
+
artifactType: "pr-check",
|
|
5
|
+
label: "PR check",
|
|
6
|
+
payload: {
|
|
7
|
+
pullRequest: "",
|
|
8
|
+
reviewFindings: [],
|
|
9
|
+
risks: [],
|
|
10
|
+
shipReadinessArtifact: "ship-readiness",
|
|
11
|
+
status: "draft",
|
|
12
|
+
summary: "",
|
|
13
|
+
},
|
|
14
|
+
requiredPhase: "ship",
|
|
15
|
+
});
|