@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,381 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { getIssuePaths, readIssueState, type IssueState } from "./state";
|
|
4
|
+
import { listArtifacts } from "./artifacts";
|
|
5
|
+
import type { ResumePacket } from "./checkpoint";
|
|
6
|
+
|
|
7
|
+
export type ResumeConfidence = "fresh" | "stale_resume" | "missing_resume" | "invalid_resume";
|
|
8
|
+
|
|
9
|
+
export interface IssueContextResult {
|
|
10
|
+
schemaVersion: 1;
|
|
11
|
+
issueId: string;
|
|
12
|
+
currentPhase: string;
|
|
13
|
+
title?: string;
|
|
14
|
+
confidence: ResumeConfidence;
|
|
15
|
+
confidenceReasons: string[];
|
|
16
|
+
resumePhase?: string;
|
|
17
|
+
resumeWrittenAt?: string;
|
|
18
|
+
checkpointPath?: string;
|
|
19
|
+
checkpointExists: boolean;
|
|
20
|
+
requiredReads: string[];
|
|
21
|
+
agentInstructions: string[];
|
|
22
|
+
next: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface BuildContextInput {
|
|
26
|
+
root?: string;
|
|
27
|
+
issueId: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function buildIssueContext(input: BuildContextInput): IssueContextResult {
|
|
31
|
+
const root = input.root ?? process.cwd();
|
|
32
|
+
const paths = getIssuePaths(root, input.issueId);
|
|
33
|
+
const state = readIssueState({ root, issueId: input.issueId });
|
|
34
|
+
|
|
35
|
+
const resumeResult = readResumeWithGrace(paths.issueDir);
|
|
36
|
+
const artifacts = listArtifactsWithGrace({ root, issueId: input.issueId });
|
|
37
|
+
const lastEventTimestamp = getLastEventTimestamp(paths.issueDir);
|
|
38
|
+
|
|
39
|
+
const confidence = computeConfidence({
|
|
40
|
+
state,
|
|
41
|
+
issueDir: paths.issueDir,
|
|
42
|
+
resume: resumeResult.packet,
|
|
43
|
+
resumeError: resumeResult.error,
|
|
44
|
+
lastEventTimestamp,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const confidenceReasons = buildConfidenceReasons({
|
|
48
|
+
state,
|
|
49
|
+
resume: resumeResult.packet,
|
|
50
|
+
resumeError: resumeResult.error,
|
|
51
|
+
lastEventTimestamp,
|
|
52
|
+
checkpointExists: resumeResult.checkpointExists,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const requiredReads = buildRequiredReads({ state, artifacts, resume: resumeResult.packet });
|
|
56
|
+
const agentInstructions = buildAgentInstructions({ state, confidence, resume: resumeResult.packet });
|
|
57
|
+
const next = buildNextGuidance(state);
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
schemaVersion: 1,
|
|
61
|
+
issueId: state.issueId,
|
|
62
|
+
currentPhase: state.currentPhase,
|
|
63
|
+
title: resumeResult.packet?.title ?? undefined,
|
|
64
|
+
confidence,
|
|
65
|
+
confidenceReasons,
|
|
66
|
+
resumePhase: resumeResult.packet?.phase ?? undefined,
|
|
67
|
+
resumeWrittenAt: resumeResult.packet?.writtenAt ?? undefined,
|
|
68
|
+
checkpointPath: resumeResult.packet?.checkpointPath ?? undefined,
|
|
69
|
+
checkpointExists: resumeResult.checkpointExists,
|
|
70
|
+
requiredReads,
|
|
71
|
+
agentInstructions,
|
|
72
|
+
next,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface ResumeReadResult {
|
|
77
|
+
packet: ResumePacket | null;
|
|
78
|
+
error: string | null;
|
|
79
|
+
checkpointExists: boolean;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function readResumeWithGrace(issueDir: string): ResumeReadResult {
|
|
83
|
+
const latestIndexPath = join(issueDir, "memory", "latest-resume-packet.json");
|
|
84
|
+
let resumePath: string | null = null;
|
|
85
|
+
let raw: unknown = null;
|
|
86
|
+
let parseError: string | null = null;
|
|
87
|
+
|
|
88
|
+
if (existsSync(latestIndexPath)) {
|
|
89
|
+
try {
|
|
90
|
+
const index = JSON.parse(readFileSync(latestIndexPath, "utf8")) as { path?: string };
|
|
91
|
+
if (index.path) {
|
|
92
|
+
const candidate = join(issueDir, index.path);
|
|
93
|
+
if (existsSync(candidate)) {
|
|
94
|
+
resumePath = candidate;
|
|
95
|
+
raw = JSON.parse(readFileSync(candidate, "utf8"));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
} catch {
|
|
99
|
+
parseError = "latest resume packet index is not valid JSON";
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!resumePath) {
|
|
104
|
+
const oldPath = join(issueDir, "memory", "resume-packet.json");
|
|
105
|
+
if (existsSync(oldPath)) {
|
|
106
|
+
resumePath = oldPath;
|
|
107
|
+
try {
|
|
108
|
+
raw = JSON.parse(readFileSync(oldPath, "utf8"));
|
|
109
|
+
} catch {
|
|
110
|
+
parseError = "resume packet is not valid JSON";
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!resumePath) {
|
|
116
|
+
return { packet: null, error: null, checkpointExists: false };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (parseError) {
|
|
120
|
+
return { packet: null, error: parseError, checkpointExists: false };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!raw || typeof raw !== "object") {
|
|
124
|
+
return { packet: null, error: "resume packet is not an object", checkpointExists: false };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const record = raw as Record<string, unknown>;
|
|
128
|
+
const phase = typeof record.phase === "string" ? record.phase : "";
|
|
129
|
+
const writtenAt = typeof record.writtenAt === "string" ? record.writtenAt : "";
|
|
130
|
+
const checkpointPath = typeof record.checkpointPath === "string" ? record.checkpointPath : "";
|
|
131
|
+
const title = typeof record.title === "string" ? record.title : "";
|
|
132
|
+
|
|
133
|
+
if (!phase || !writtenAt) {
|
|
134
|
+
return { packet: null, error: "resume packet missing required fields (phase, writtenAt)", checkpointExists: false };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const checkpointExists = checkpointPath ? existsSync(join(issueDir, checkpointPath)) : false;
|
|
138
|
+
|
|
139
|
+
const packet: ResumePacket = {
|
|
140
|
+
schemaVersion: 1,
|
|
141
|
+
issueId: String(record.issueId ?? ""),
|
|
142
|
+
phase,
|
|
143
|
+
title,
|
|
144
|
+
status: typeof record.status === "string" ? record.status : "in-progress",
|
|
145
|
+
branch: typeof record.branch === "string" ? record.branch : "unknown",
|
|
146
|
+
writtenAt,
|
|
147
|
+
checkpointPath,
|
|
148
|
+
summary: typeof record.summary === "string" ? record.summary : "",
|
|
149
|
+
decisions: normalizeStringArray(record.decisions),
|
|
150
|
+
remainingWork: normalizeStringArray(record.remainingWork),
|
|
151
|
+
notes: normalizeStringArray(record.notes),
|
|
152
|
+
filesModified: normalizeStringArray(record.filesModified),
|
|
153
|
+
...(typeof record.sessionDurationSeconds === "number" && Number.isFinite(record.sessionDurationSeconds)
|
|
154
|
+
? { sessionDurationSeconds: record.sessionDurationSeconds }
|
|
155
|
+
: {}),
|
|
156
|
+
...(typeof record.parentCheckpointId === "string" && record.parentCheckpointId.trim()
|
|
157
|
+
? { parentCheckpointId: record.parentCheckpointId }
|
|
158
|
+
: {}),
|
|
159
|
+
...(typeof record.transitionReason === "string" && record.transitionReason.trim()
|
|
160
|
+
? { transitionReason: record.transitionReason }
|
|
161
|
+
: {}),
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
return { packet, error: null, checkpointExists };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function listArtifactsWithGrace(input: { root: string; issueId: string }): string[] {
|
|
168
|
+
try {
|
|
169
|
+
return listArtifacts(input).map((a) => a.type);
|
|
170
|
+
} catch {
|
|
171
|
+
return [];
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function getLastEventTimestamp(issueDir: string): string | null {
|
|
176
|
+
const eventsPath = join(issueDir, "events.jsonl");
|
|
177
|
+
if (!existsSync(eventsPath)) return null;
|
|
178
|
+
|
|
179
|
+
const content = readFileSync(eventsPath, "utf8");
|
|
180
|
+
const lines = content.split("\n").filter(Boolean);
|
|
181
|
+
if (lines.length === 0) return null;
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
const lastEvent = JSON.parse(lines[lines.length - 1]) as { timestamp?: string };
|
|
185
|
+
return lastEvent.timestamp ?? null;
|
|
186
|
+
} catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
interface ConfidenceInput {
|
|
192
|
+
state: IssueState;
|
|
193
|
+
issueDir: string;
|
|
194
|
+
resume: ResumePacket | null;
|
|
195
|
+
resumeError: string | null;
|
|
196
|
+
lastEventTimestamp: string | null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function computeConfidence(input: ConfidenceInput): ResumeConfidence {
|
|
200
|
+
const { state, issueDir, resume, resumeError } = input;
|
|
201
|
+
|
|
202
|
+
if (resumeError) {
|
|
203
|
+
return "invalid_resume";
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (!resume) {
|
|
207
|
+
return "missing_resume";
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (resume.phase !== state.currentPhase) {
|
|
211
|
+
return "stale_resume";
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (!resume.checkpointPath || !existsSync(join(issueDir, resume.checkpointPath))) {
|
|
215
|
+
return "invalid_resume";
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const resumeTime = new Date(resume.writtenAt).getTime();
|
|
219
|
+
if (Number.isNaN(resumeTime)) {
|
|
220
|
+
return "invalid_resume";
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const stateUpdatedTime = new Date(state.updatedAt).getTime();
|
|
224
|
+
if (stateUpdatedTime > resumeTime) {
|
|
225
|
+
return "stale_resume";
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (input.lastEventTimestamp) {
|
|
229
|
+
const eventTime = new Date(input.lastEventTimestamp).getTime();
|
|
230
|
+
if (eventTime > resumeTime) {
|
|
231
|
+
return "stale_resume";
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return "fresh";
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function buildConfidenceReasons(input: {
|
|
239
|
+
state: IssueState;
|
|
240
|
+
resume: ResumePacket | null;
|
|
241
|
+
resumeError: string | null;
|
|
242
|
+
lastEventTimestamp: string | null;
|
|
243
|
+
checkpointExists: boolean;
|
|
244
|
+
}): string[] {
|
|
245
|
+
const reasons: string[] = [];
|
|
246
|
+
|
|
247
|
+
if (input.resumeError) {
|
|
248
|
+
reasons.push(input.resumeError);
|
|
249
|
+
return reasons;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (!input.resume) {
|
|
253
|
+
reasons.push("no resume packet found");
|
|
254
|
+
return reasons;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (input.resume.phase !== input.state.currentPhase) {
|
|
258
|
+
reasons.push(`resume phase (${input.resume.phase}) differs from current phase (${input.state.currentPhase})`);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (!input.checkpointExists) {
|
|
262
|
+
reasons.push(`checkpoint path does not exist: ${input.resume.checkpointPath ?? "none"}`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const resumeTime = new Date(input.resume.writtenAt).getTime();
|
|
266
|
+
const stateUpdatedTime = new Date(input.state.updatedAt).getTime();
|
|
267
|
+
if (stateUpdatedTime > resumeTime) {
|
|
268
|
+
reasons.push(`state updatedAt (${input.state.updatedAt}) is newer than resume writtenAt (${input.resume.writtenAt})`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (input.lastEventTimestamp) {
|
|
272
|
+
const eventTime = new Date(input.lastEventTimestamp).getTime();
|
|
273
|
+
if (eventTime > resumeTime) {
|
|
274
|
+
reasons.push(`latest event timestamp (${input.lastEventTimestamp}) is newer than resume writtenAt (${input.resume.writtenAt})`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (reasons.length === 0) {
|
|
279
|
+
reasons.push("resume packet is consistent with current state and events");
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return reasons;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function buildRequiredReads(input: {
|
|
286
|
+
state: IssueState;
|
|
287
|
+
artifacts: string[];
|
|
288
|
+
resume: ResumePacket | null;
|
|
289
|
+
}): string[] {
|
|
290
|
+
const reads: string[] = [];
|
|
291
|
+
reads.push(`.gxpm/issues/${input.state.issueId}/state.json`);
|
|
292
|
+
reads.push(`.gxpm/issues/${input.state.issueId}/events.jsonl`);
|
|
293
|
+
|
|
294
|
+
const currentPhaseArtifact = getCurrentPhaseArtifact(input.state.currentPhase);
|
|
295
|
+
if (currentPhaseArtifact && input.artifacts.includes(currentPhaseArtifact)) {
|
|
296
|
+
reads.push(`.gxpm/issues/${input.state.issueId}/artifacts/${currentPhaseArtifact}.json`);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (input.resume?.checkpointPath) {
|
|
300
|
+
reads.push(`.gxpm/issues/${input.state.issueId}/${input.resume.checkpointPath}`);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return reads;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function buildAgentInstructions(input: {
|
|
307
|
+
state: IssueState;
|
|
308
|
+
confidence: ResumeConfidence;
|
|
309
|
+
resume: ResumePacket | null;
|
|
310
|
+
}): string[] {
|
|
311
|
+
const instructions: string[] = [];
|
|
312
|
+
|
|
313
|
+
switch (input.confidence) {
|
|
314
|
+
case "fresh":
|
|
315
|
+
instructions.push("Resume context is fresh. You may use the resume packet and checkpoint as supplementary context, but state.json and events.jsonl remain the highest-priority truth sources.");
|
|
316
|
+
if (input.resume?.remainingWork && input.resume.remainingWork.length > 0) {
|
|
317
|
+
instructions.push(`Remaining work from checkpoint: ${input.resume.remainingWork.join("; ")}`);
|
|
318
|
+
}
|
|
319
|
+
break;
|
|
320
|
+
case "stale_resume":
|
|
321
|
+
instructions.push("Resume context is stale. Rebuild your understanding from state.json, events.jsonl, and current phase artifacts. Do not rely on the resume packet for remaining work.");
|
|
322
|
+
break;
|
|
323
|
+
case "missing_resume":
|
|
324
|
+
instructions.push("No resume packet found. Build context entirely from state.json, events.jsonl, and current phase artifacts.");
|
|
325
|
+
break;
|
|
326
|
+
case "invalid_resume":
|
|
327
|
+
instructions.push("Resume packet is invalid or unreadable. Ignore it and build context from state.json, events.jsonl, and current phase artifacts.");
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
instructions.push(`Current phase is ${input.state.currentPhase}. Run gxpm issue next ${input.state.issueId} if you are unsure what to do next.`);
|
|
332
|
+
return instructions;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function buildNextGuidance(state: IssueState): string {
|
|
336
|
+
const phaseOrder = [
|
|
337
|
+
"triage",
|
|
338
|
+
"plan",
|
|
339
|
+
"dispatch",
|
|
340
|
+
"implement",
|
|
341
|
+
"local-verify",
|
|
342
|
+
"ac-check",
|
|
343
|
+
"self-review",
|
|
344
|
+
"ship",
|
|
345
|
+
"pr-check",
|
|
346
|
+
"verify",
|
|
347
|
+
"qa",
|
|
348
|
+
"land",
|
|
349
|
+
] as const;
|
|
350
|
+
|
|
351
|
+
const idx = phaseOrder.indexOf(state.currentPhase as (typeof phaseOrder)[number]);
|
|
352
|
+
if (idx < 0) return `Unknown phase: ${state.currentPhase}`;
|
|
353
|
+
if (idx >= phaseOrder.length - 1) return "Terminal phase reached.";
|
|
354
|
+
|
|
355
|
+
const nextPhase = phaseOrder[idx + 1];
|
|
356
|
+
return `Next phase: ${nextPhase}. Run gxpm issue transition ${state.issueId} ${nextPhase} when ready.`;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function getCurrentPhaseArtifact(phase: string): string | null {
|
|
360
|
+
const map: Record<string, string> = {
|
|
361
|
+
triage: "acceptance-contract",
|
|
362
|
+
plan: "implementation-plan",
|
|
363
|
+
dispatch: "dispatch-handoff",
|
|
364
|
+
implement: "local-verify",
|
|
365
|
+
"local-verify": "acceptance-check",
|
|
366
|
+
"ac-check": "self-review",
|
|
367
|
+
"self-review": "cleanup-report",
|
|
368
|
+
cleanup: "ship-readiness",
|
|
369
|
+
ship: "pr-check",
|
|
370
|
+
"pr-check": "verify-findings",
|
|
371
|
+
verify: "qa-findings",
|
|
372
|
+
qa: "land-findings",
|
|
373
|
+
land: "land-findings",
|
|
374
|
+
};
|
|
375
|
+
return map[phase] ?? null;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function normalizeStringArray(value: unknown): string[] {
|
|
379
|
+
if (!Array.isArray(value)) return [];
|
|
380
|
+
return value.map((item) => String(item).trim()).filter(Boolean);
|
|
381
|
+
}
|