@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,243 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import { readGxpmVersion } from "../version";
|
|
6
|
+
|
|
7
|
+
interface UpgradeOptions {
|
|
8
|
+
dryRun?: boolean;
|
|
9
|
+
skipPull?: boolean;
|
|
10
|
+
skipInstall?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getGxpmRoot(): string {
|
|
14
|
+
const envRoot = process.env.GXPM_DIR;
|
|
15
|
+
if (envRoot) return resolve(envRoot);
|
|
16
|
+
// Heuristic: find gxpm repo by locating the bin/gxpm binary on PATH
|
|
17
|
+
try {
|
|
18
|
+
const binPath = execSync("command -v gxpm", { encoding: "utf-8" }).trim();
|
|
19
|
+
if (binPath) {
|
|
20
|
+
// Resolve symlinks
|
|
21
|
+
const realBin = execSync(`readlink -f "${binPath}" 2>/dev/null || echo "${binPath}"`, { encoding: "utf-8" }).trim();
|
|
22
|
+
return resolve(realBin, "..", "..");
|
|
23
|
+
}
|
|
24
|
+
} catch {
|
|
25
|
+
// fallthrough
|
|
26
|
+
}
|
|
27
|
+
// Fallback: assume cwd is gxpm repo
|
|
28
|
+
return resolve(import.meta.dir, "../..");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getCurrentBranch(root: string): string {
|
|
32
|
+
return execSync("git branch --show-current", { cwd: root, encoding: "utf-8" }).trim();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getLastLocalVersion(root: string): string | null {
|
|
36
|
+
try {
|
|
37
|
+
const prior = execSync("git show HEAD@{1}:VERSION", { cwd: root, encoding: "utf-8" }).trim();
|
|
38
|
+
return prior;
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function filesChangedSince(root: string, ref: string): string[] {
|
|
45
|
+
try {
|
|
46
|
+
const out = execSync(`git diff --name-only ${ref}..HEAD`, { cwd: root, encoding: "utf-8" }).trim();
|
|
47
|
+
return out ? out.split("\n") : [];
|
|
48
|
+
} catch {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function listMigrationGuides(root: string, fromVersion: string, toVersion: string): string[] {
|
|
54
|
+
const migrationsDir = join(root, "docs", "migrations");
|
|
55
|
+
if (!existsSync(migrationsDir)) return [];
|
|
56
|
+
|
|
57
|
+
const guides: string[] = [];
|
|
58
|
+
for (const file of readdirSync(migrationsDir)) {
|
|
59
|
+
if (!file.endsWith(".md")) continue;
|
|
60
|
+
const match = file.match(/^v([0-9.]+)\.md$/);
|
|
61
|
+
if (!match) continue;
|
|
62
|
+
const v = match[1];
|
|
63
|
+
// Simple semver-ish comparison: assume versions are like 0.1.0, 0.2.0
|
|
64
|
+
if (compareSemver(v, fromVersion) > 0 && compareSemver(v, toVersion) <= 0) {
|
|
65
|
+
guides.push(join(migrationsDir, file));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return guides.sort((a, b) => compareSemver(parseVersionFromPath(a), parseVersionFromPath(b)));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function parseVersionFromPath(path: string): string {
|
|
72
|
+
const base = path.split("/").pop() ?? "";
|
|
73
|
+
const m = base.match(/^v([0-9.]+)\.md$/);
|
|
74
|
+
return m?.[1] ?? "0.0.0";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function compareSemver(a: string, b: string): number {
|
|
78
|
+
const pa = a.split(".").map(Number);
|
|
79
|
+
const pb = b.split(".").map(Number);
|
|
80
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
81
|
+
const na = pa[i] ?? 0;
|
|
82
|
+
const nb = pb[i] ?? 0;
|
|
83
|
+
if (na !== nb) return na - nb;
|
|
84
|
+
}
|
|
85
|
+
return 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function logUpgradeError(root: string, phase: string, from: string, to: string, hint: string) {
|
|
89
|
+
const auditDir = join(homedir(), ".gxpm", "audit");
|
|
90
|
+
mkdirSync(auditDir, { recursive: true });
|
|
91
|
+
const line = JSON.stringify({ ts: new Date().toISOString(), phase, from_version: from, to_version: to, hint }) + "\n";
|
|
92
|
+
const errPath = join(auditDir, "upgrade-errors.jsonl");
|
|
93
|
+
const { writeFileSync } = require("node:fs");
|
|
94
|
+
writeFileSync(errPath, line, { flag: "a" });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
import { mkdirSync } from "node:fs";
|
|
98
|
+
|
|
99
|
+
export function runUpgradeCommand(argv: string[]) {
|
|
100
|
+
const opts: UpgradeOptions = {
|
|
101
|
+
dryRun: argv.includes("--dry-run"),
|
|
102
|
+
skipPull: argv.includes("--skip-pull"),
|
|
103
|
+
skipInstall: argv.includes("--skip-install"),
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const root = getGxpmRoot();
|
|
107
|
+
const currentVersion = readGxpmVersion({ root });
|
|
108
|
+
const priorVersion = getLastLocalVersion(root);
|
|
109
|
+
const branch = getCurrentBranch(root);
|
|
110
|
+
|
|
111
|
+
if (branch !== "main" && branch !== "master") {
|
|
112
|
+
console.warn(`Warning: current branch is '${branch}', not main/master. Upgrade may be unsafe.`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const steps: string[] = [];
|
|
116
|
+
|
|
117
|
+
// Step 1: git pull
|
|
118
|
+
if (!opts.skipPull) {
|
|
119
|
+
steps.push("git pull origin main");
|
|
120
|
+
if (!opts.dryRun) {
|
|
121
|
+
try {
|
|
122
|
+
execSync("git pull origin main", { cwd: root, stdio: "inherit" });
|
|
123
|
+
} catch (e) {
|
|
124
|
+
const hint = "Resolve git conflicts manually, then rerun `gxpm upgrade`";
|
|
125
|
+
logUpgradeError(root, "git_pull", priorVersion ?? "unknown", currentVersion, hint);
|
|
126
|
+
throw new Error(`git pull failed. ${hint}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Step 2: bun install
|
|
132
|
+
if (!opts.skipInstall) {
|
|
133
|
+
steps.push("bun install");
|
|
134
|
+
if (!opts.dryRun) {
|
|
135
|
+
try {
|
|
136
|
+
execSync("bun install", { cwd: root, stdio: "inherit" });
|
|
137
|
+
} catch (e) {
|
|
138
|
+
const hint = "Check bun.lock conflicts and run `bun install` manually";
|
|
139
|
+
logUpgradeError(root, "bun_install", priorVersion ?? "unknown", currentVersion, hint);
|
|
140
|
+
throw new Error(`bun install failed. ${hint}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Step 3: Detect template changes and regenerate skill docs
|
|
146
|
+
const changed = opts.dryRun ? [] : filesChangedSince(root, "HEAD@{1}");
|
|
147
|
+
const tmplChanged = changed.some((f) => f.endsWith(".tmpl"));
|
|
148
|
+
const hooksChanged = changed.some((f) => f.startsWith("templates/hooks/"));
|
|
149
|
+
|
|
150
|
+
if (tmplChanged) {
|
|
151
|
+
steps.push("bun run gen:skill-docs (skill templates changed)");
|
|
152
|
+
if (!opts.dryRun) {
|
|
153
|
+
try {
|
|
154
|
+
execSync("bun run gen:skill-docs", { cwd: root, stdio: "inherit" });
|
|
155
|
+
} catch (e) {
|
|
156
|
+
const hint = "Run `bun run gen:skill-docs` manually after fixing errors";
|
|
157
|
+
logUpgradeError(root, "gen_skill_docs", priorVersion ?? "unknown", currentVersion, hint);
|
|
158
|
+
throw new Error(`gen:skill-docs failed. ${hint}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (hooksChanged) {
|
|
164
|
+
steps.push("gxpm init --install-hooks (hook templates changed) — run manually");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Step 4: post-upgrade notes
|
|
168
|
+
const newVersion = readGxpmVersion({ root });
|
|
169
|
+
const guides = listMigrationGuides(root, priorVersion ?? "0.0.0", newVersion);
|
|
170
|
+
|
|
171
|
+
if (opts.dryRun) {
|
|
172
|
+
console.log("DRY RUN — would execute:");
|
|
173
|
+
for (const s of steps) console.log(` ${s}`);
|
|
174
|
+
if (guides.length > 0) {
|
|
175
|
+
console.log("");
|
|
176
|
+
console.log("Migration guides to read:");
|
|
177
|
+
for (const g of guides) console.log(` ${g}`);
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
console.log(`Upgrade complete: ${priorVersion ?? "unknown"} -> ${newVersion}`);
|
|
183
|
+
console.log("");
|
|
184
|
+
if (guides.length > 0) {
|
|
185
|
+
console.log("Migration guides for versions you skipped:");
|
|
186
|
+
for (const g of guides) {
|
|
187
|
+
console.log(` ${g}`);
|
|
188
|
+
}
|
|
189
|
+
console.log("");
|
|
190
|
+
console.log("Read each guide and run any backfill steps listed before continuing.");
|
|
191
|
+
}
|
|
192
|
+
if (hooksChanged) {
|
|
193
|
+
console.log("");
|
|
194
|
+
console.log("Hook templates changed. Run `gxpm init --install-hooks --target <repo>` to update.");
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function runPostUpgradeCommand(argv: string[]) {
|
|
199
|
+
const root = getGxpmRoot();
|
|
200
|
+
const currentVersion = readGxpmVersion({ root });
|
|
201
|
+
|
|
202
|
+
// Find all migration guides <= current version
|
|
203
|
+
const migrationsDir = join(root, "docs", "migrations");
|
|
204
|
+
if (!existsSync(migrationsDir)) {
|
|
205
|
+
console.log("No migrations directory found.");
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const guides: string[] = [];
|
|
210
|
+
for (const file of readdirSync(migrationsDir)) {
|
|
211
|
+
if (!file.endsWith(".md")) continue;
|
|
212
|
+
const v = parseVersionFromPath(file);
|
|
213
|
+
if (compareSemver(v, currentVersion) <= 0) {
|
|
214
|
+
guides.push(join(migrationsDir, file));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
guides.sort((a, b) => compareSemver(parseVersionFromPath(a), parseVersionFromPath(b)));
|
|
218
|
+
|
|
219
|
+
console.log(`gxpm ${currentVersion} — post-upgrade notes`);
|
|
220
|
+
console.log("");
|
|
221
|
+
|
|
222
|
+
if (guides.length === 0) {
|
|
223
|
+
console.log("No migration guides found.");
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
for (const g of guides) {
|
|
228
|
+
const name = g.split("/").pop() ?? g;
|
|
229
|
+
console.log(`--- ${name} ---`);
|
|
230
|
+
try {
|
|
231
|
+
const content = readFileSync(g, "utf-8");
|
|
232
|
+
// Print only the first 30 lines to avoid flooding
|
|
233
|
+
const lines = content.split("\n").slice(0, 30);
|
|
234
|
+
console.log(lines.join("\n"));
|
|
235
|
+
if (content.split("\n").length > 30) {
|
|
236
|
+
console.log("... (truncated, read full file for details)");
|
|
237
|
+
}
|
|
238
|
+
console.log("");
|
|
239
|
+
} catch {
|
|
240
|
+
console.log("(could not read)");
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import { getConfigValue } from "../../core/config";
|
|
6
|
+
import { runDoctor } from "../doctor";
|
|
7
|
+
|
|
8
|
+
interface VerifyCheck {
|
|
9
|
+
name: string;
|
|
10
|
+
status: "ok" | "warn" | "fail";
|
|
11
|
+
message: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface VerifyResult {
|
|
15
|
+
status: "passed" | "warnings" | "failed";
|
|
16
|
+
checks: VerifyCheck[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isGitRepo(dir: string): boolean {
|
|
20
|
+
return existsSync(join(dir, ".git"));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function checkGitHooksFire(target: string): VerifyCheck {
|
|
24
|
+
try {
|
|
25
|
+
// Create a temp file to verify pre-commit fires
|
|
26
|
+
const testFile = join(target, ".gxpm", ".verify-hook-test");
|
|
27
|
+
writeFileSync(testFile, "hook test\n");
|
|
28
|
+
execSync("git add -f .gxpm/.verify-hook-test", { cwd: target });
|
|
29
|
+
// Run pre-commit hook manually to see if it exits 0
|
|
30
|
+
const hooksPath = execSync("git config core.hooksPath", { cwd: target, encoding: "utf-8" }).trim();
|
|
31
|
+
const preCommit = join(hooksPath, "pre-commit");
|
|
32
|
+
if (existsSync(preCommit)) {
|
|
33
|
+
execSync(preCommit, { cwd: target, stdio: "ignore" });
|
|
34
|
+
}
|
|
35
|
+
execSync("git reset HEAD .gxpm/.verify-hook-test", { cwd: target, stdio: "ignore" });
|
|
36
|
+
rmSync(testFile);
|
|
37
|
+
return { name: "git_hooks_fire", status: "ok", message: "pre-commit hook executes without error" };
|
|
38
|
+
} catch (e) {
|
|
39
|
+
return { name: "git_hooks_fire", status: "warn", message: `Could not verify hook execution: ${e instanceof Error ? e.message : String(e)}` };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function checkWorktreeAvailable(target: string): VerifyCheck {
|
|
44
|
+
try {
|
|
45
|
+
execSync("git worktree list", { cwd: target, stdio: "ignore" });
|
|
46
|
+
return { name: "worktree_available", status: "ok", message: "git worktree is available" };
|
|
47
|
+
} catch {
|
|
48
|
+
return { name: "worktree_available", status: "warn", message: "git worktree not available or failed" };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function checkSkillHostDiscovery(): VerifyCheck {
|
|
53
|
+
const hosts = ["claude", "codex", "cursor"];
|
|
54
|
+
const found: string[] = [];
|
|
55
|
+
const home = homedir();
|
|
56
|
+
for (const h of hosts) {
|
|
57
|
+
const skillPath = join(home, "." + h, "skills", "gxpm", "SKILL.md");
|
|
58
|
+
if (existsSync(skillPath)) found.push(h);
|
|
59
|
+
}
|
|
60
|
+
if (found.length > 0) {
|
|
61
|
+
return { name: "skill_host_discovery", status: "ok", message: `gxpm skill installed on: ${found.join(", ")}` };
|
|
62
|
+
}
|
|
63
|
+
return { name: "skill_host_discovery", status: "fail", message: "gxpm skill not found on any host. Run: gxpm init --install-skill --host all" };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function checkLinearConnectivity(): VerifyCheck {
|
|
67
|
+
const provider = getConfigValue({ key: "sync.provider" });
|
|
68
|
+
if (provider.value !== "linear") {
|
|
69
|
+
return { name: "linear_connectivity", status: "ok", message: "sync.provider is not linear; skipped" };
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const token = process.env.LINEAR_API_KEY || process.env.LINEAR_API_TOKEN;
|
|
73
|
+
if (!token) {
|
|
74
|
+
return { name: "linear_connectivity", status: "warn", message: "sync.provider=linear but no LINEAR_API_KEY env var set" };
|
|
75
|
+
}
|
|
76
|
+
// Minimal GraphQL health check
|
|
77
|
+
const result = execSync(
|
|
78
|
+
`curl -sf -H "Authorization: ${token}" -H "Content-Type: application/json" -X POST -d '{"query":"{ viewer { id } }"}' https://api.linear.app/graphql`,
|
|
79
|
+
{ encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }
|
|
80
|
+
);
|
|
81
|
+
const parsed = JSON.parse(result);
|
|
82
|
+
if (parsed.errors) {
|
|
83
|
+
return { name: "linear_connectivity", status: "fail", message: `Linear API error: ${parsed.errors[0]?.message}` };
|
|
84
|
+
}
|
|
85
|
+
return { name: "linear_connectivity", status: "ok", message: "Linear API reachable" };
|
|
86
|
+
} catch (e) {
|
|
87
|
+
return { name: "linear_connectivity", status: "warn", message: `Could not verify Linear connectivity: ${e instanceof Error ? e.message : String(e)}` };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function checkIssueLifecycle(target: string, dryRun: boolean): VerifyCheck {
|
|
92
|
+
try {
|
|
93
|
+
if (dryRun) {
|
|
94
|
+
return { name: "issue_lifecycle", status: "ok", message: "dry-run: skipped actual issue creation" };
|
|
95
|
+
}
|
|
96
|
+
// Create a test issue
|
|
97
|
+
const out = execSync("bin/gxpm issue create --auto-id", { cwd: target, encoding: "utf-8" }).trim();
|
|
98
|
+
const match = out.match(/created (GXPM-\d+)/i);
|
|
99
|
+
if (!match) {
|
|
100
|
+
return { name: "issue_lifecycle", status: "fail", message: "Could not parse issue creation output" };
|
|
101
|
+
}
|
|
102
|
+
const id = match[1];
|
|
103
|
+
// Initialize required artifact for triage → plan transition
|
|
104
|
+
execSync(`bin/gxpm triage init ${id}`, { cwd: target, stdio: "ignore" });
|
|
105
|
+
// Transition to plan
|
|
106
|
+
execSync(`bin/gxpm issue transition ${id} plan`, { cwd: target, stdio: "ignore" });
|
|
107
|
+
return { name: "issue_lifecycle", status: "ok", message: `Created and transitioned ${id} successfully` };
|
|
108
|
+
} catch (e) {
|
|
109
|
+
return { name: "issue_lifecycle", status: "fail", message: `Issue lifecycle test failed: ${e instanceof Error ? e.message : String(e)}` };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function runVerifyCommand(argv: string[]) {
|
|
114
|
+
const dryRun = argv.includes("--dry-run");
|
|
115
|
+
const json = argv.includes("--json");
|
|
116
|
+
const target = resolve(process.cwd());
|
|
117
|
+
|
|
118
|
+
if (!isGitRepo(target)) {
|
|
119
|
+
throw new Error(`Not a git repository: ${target}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const checks: VerifyCheck[] = [];
|
|
123
|
+
|
|
124
|
+
// 1. Doctor baseline
|
|
125
|
+
try {
|
|
126
|
+
const doctorReport = runDoctor({ cwd: target });
|
|
127
|
+
const hasFail = (doctorReport as any).checks?.some((c: any) => c.status === "fail");
|
|
128
|
+
const hasWarn = (doctorReport as any).checks?.some((c: any) => c.status === "warn");
|
|
129
|
+
if (hasFail) {
|
|
130
|
+
checks.push({ name: "doctor_baseline", status: "fail", message: "gxpm doctor has failing checks" });
|
|
131
|
+
} else if (hasWarn) {
|
|
132
|
+
checks.push({ name: "doctor_baseline", status: "warn", message: "gxpm doctor has warnings" });
|
|
133
|
+
} else {
|
|
134
|
+
checks.push({ name: "doctor_baseline", status: "ok", message: "gxpm doctor all clear" });
|
|
135
|
+
}
|
|
136
|
+
} catch (e) {
|
|
137
|
+
checks.push({ name: "doctor_baseline", status: "fail", message: `doctor failed: ${e instanceof Error ? e.message : String(e)}` });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 2. Git hooks
|
|
141
|
+
checks.push(checkGitHooksFire(target));
|
|
142
|
+
|
|
143
|
+
// 3. Worktree
|
|
144
|
+
checks.push(checkWorktreeAvailable(target));
|
|
145
|
+
|
|
146
|
+
// 4. Skill hosts
|
|
147
|
+
checks.push(checkSkillHostDiscovery());
|
|
148
|
+
|
|
149
|
+
// 5. Linear (if configured)
|
|
150
|
+
checks.push(checkLinearConnectivity());
|
|
151
|
+
|
|
152
|
+
// 6. Issue lifecycle (optional, can be skipped with --no-issue-test)
|
|
153
|
+
if (!argv.includes("--no-issue-test")) {
|
|
154
|
+
checks.push(checkIssueLifecycle(target, dryRun));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const hasFail = checks.some((c) => c.status === "fail");
|
|
158
|
+
const hasWarn = checks.some((c) => c.status === "warn");
|
|
159
|
+
const status: VerifyResult["status"] = hasFail ? "failed" : hasWarn ? "warnings" : "passed";
|
|
160
|
+
|
|
161
|
+
const result: VerifyResult = { status, checks };
|
|
162
|
+
|
|
163
|
+
if (json) {
|
|
164
|
+
console.log(JSON.stringify(result, null, 2));
|
|
165
|
+
process.exit(hasFail ? 1 : 0);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
console.log("gxpm verify");
|
|
169
|
+
console.log("===========");
|
|
170
|
+
console.log("");
|
|
171
|
+
for (const c of checks) {
|
|
172
|
+
const icon = c.status === "ok" ? "✓" : c.status === "warn" ? "⚠" : "✗";
|
|
173
|
+
console.log(` ${icon} ${c.name}: ${c.message}`);
|
|
174
|
+
}
|
|
175
|
+
console.log("");
|
|
176
|
+
console.log(`Result: ${status}`);
|
|
177
|
+
|
|
178
|
+
if (hasFail) {
|
|
179
|
+
console.log("");
|
|
180
|
+
console.log("Fix failing checks, then rerun `gxpm verify`.");
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { writeArtifact } from "../../core/artifacts";
|
|
2
|
+
import {
|
|
3
|
+
ensureNativeWikiCurrent,
|
|
4
|
+
evaluateNativeWiki,
|
|
5
|
+
getNativeWikiContextForIssue,
|
|
6
|
+
getNativeWikiStatus,
|
|
7
|
+
initializeNativeWiki,
|
|
8
|
+
queryNativeWiki,
|
|
9
|
+
updateNativeWiki,
|
|
10
|
+
type NativeWikiBuildResult,
|
|
11
|
+
type NativeWikiEvalReport,
|
|
12
|
+
type NativeWikiIssueContext,
|
|
13
|
+
type NativeWikiQueryResult,
|
|
14
|
+
type NativeWikiStatus,
|
|
15
|
+
} from "../../core/wiki";
|
|
16
|
+
import { optionRequiredValue, parsePositiveIntegerOption } from "./helpers";
|
|
17
|
+
|
|
18
|
+
const WIKI_CONTEXT_USAGE = "Usage: gxpm wiki context <issue-id> [--phase <phase>] [--limit <n>] [--write-artifact] [--json] [--no-auto-update]";
|
|
19
|
+
|
|
20
|
+
export function runWikiCommand(argv: string[], subcommand: string | undefined) {
|
|
21
|
+
if (!subcommand || subcommand === "status") {
|
|
22
|
+
const native = getNativeWikiStatus();
|
|
23
|
+
if (argv.includes("--json")) {
|
|
24
|
+
console.log(JSON.stringify({ native }, null, 2));
|
|
25
|
+
} else {
|
|
26
|
+
console.log(formatNativeWikiStatus(native));
|
|
27
|
+
}
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (subcommand === "init" || subcommand === "index") {
|
|
32
|
+
const result = initializeNativeWiki();
|
|
33
|
+
if (argv.includes("--json")) {
|
|
34
|
+
console.log(JSON.stringify(result, null, 2));
|
|
35
|
+
} else {
|
|
36
|
+
console.log(formatNativeWikiBuildResult(result));
|
|
37
|
+
}
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (subcommand === "update") {
|
|
42
|
+
const result = updateNativeWiki();
|
|
43
|
+
if (argv.includes("--json")) {
|
|
44
|
+
console.log(JSON.stringify(result, null, 2));
|
|
45
|
+
} else {
|
|
46
|
+
console.log(formatNativeWikiBuildResult(result));
|
|
47
|
+
}
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (subcommand === "eval") {
|
|
52
|
+
const report = evaluateNativeWiki();
|
|
53
|
+
if (argv.includes("--json")) {
|
|
54
|
+
console.log(JSON.stringify(report, null, 2));
|
|
55
|
+
} else {
|
|
56
|
+
console.log(formatNativeWikiEvalReport(report));
|
|
57
|
+
}
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (subcommand === "query") {
|
|
62
|
+
const query = wikiQueryText(argv);
|
|
63
|
+
if (!query) {
|
|
64
|
+
throw new Error("Usage: gxpm wiki query <text> [--limit <n>] [--json] [--no-auto-update]");
|
|
65
|
+
}
|
|
66
|
+
const result = queryNativeWiki({
|
|
67
|
+
query,
|
|
68
|
+
limit: parsePositiveIntegerOption(argv, "--limit"),
|
|
69
|
+
autoUpdate: !argv.includes("--no-auto-update"),
|
|
70
|
+
});
|
|
71
|
+
if (argv.includes("--json")) {
|
|
72
|
+
console.log(JSON.stringify(result, null, 2));
|
|
73
|
+
} else {
|
|
74
|
+
console.log(formatNativeWikiQueryResult(result));
|
|
75
|
+
}
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (subcommand === "context") {
|
|
80
|
+
const contextIssueId = argv[2];
|
|
81
|
+
if (!contextIssueId || contextIssueId.startsWith("--")) {
|
|
82
|
+
throw new Error(WIKI_CONTEXT_USAGE);
|
|
83
|
+
}
|
|
84
|
+
assertNoUnexpectedWikiContextPositionals(argv);
|
|
85
|
+
const result = getNativeWikiContextForIssue({
|
|
86
|
+
issueId: contextIssueId,
|
|
87
|
+
phase: argv.includes("--phase") ? optionRequiredValue(argv, "--phase") : undefined,
|
|
88
|
+
limit: parsePositiveIntegerOption(argv, "--limit"),
|
|
89
|
+
autoUpdate: !argv.includes("--no-auto-update"),
|
|
90
|
+
});
|
|
91
|
+
const artifactWritten = argv.includes("--write-artifact");
|
|
92
|
+
if (artifactWritten) {
|
|
93
|
+
writeArtifact({ issueId: contextIssueId, type: "wiki-context", payload: result });
|
|
94
|
+
}
|
|
95
|
+
if (argv.includes("--json")) {
|
|
96
|
+
console.log(JSON.stringify(artifactWritten ? { ...result, artifactWritten: "wiki-context" } : result, null, 2));
|
|
97
|
+
} else {
|
|
98
|
+
console.log(formatNativeWikiIssueContext(result, artifactWritten));
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
throw new Error(`Usage: gxpm wiki status [--json] | gxpm wiki init [--json] | gxpm wiki index [--json] | gxpm wiki update [--json] | gxpm wiki eval [--json] | gxpm wiki query <text> [--limit <n>] [--json] | ${WIKI_CONTEXT_USAGE.replace(/^Usage: /, "")}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function formatNativeWikiStatus(status: NativeWikiStatus) {
|
|
107
|
+
const lines = [`Optional human wiki: ${status.state}`];
|
|
108
|
+
if (!status.detected) {
|
|
109
|
+
lines.push(`state: ${status.paths.state} not initialized`);
|
|
110
|
+
lines.push(`Reason: ${status.reason}`);
|
|
111
|
+
lines.push(`Run when humans want local project docs: ${status.commands.init}`);
|
|
112
|
+
return lines.join("\n");
|
|
113
|
+
}
|
|
114
|
+
lines.push(`generatedAt: ${status.generatedAt ?? "unknown"}`);
|
|
115
|
+
lines.push(`baseCommit: ${status.baseCommit ?? "unknown"}`);
|
|
116
|
+
lines.push(`currentCommit: ${status.currentCommit ?? "unknown"}`);
|
|
117
|
+
lines.push(`files: ${status.indexedFiles}`);
|
|
118
|
+
lines.push(`edges: ${status.graphEdges}`);
|
|
119
|
+
lines.push(`dimensions: ${status.dimensionedFiles}`);
|
|
120
|
+
lines.push(`docs: ${status.docs.join(", ") || "none"}`);
|
|
121
|
+
lines.push(`stale: ${status.stale ? "yes" : "no"}`);
|
|
122
|
+
lines.push(`Reason: ${status.reason}`);
|
|
123
|
+
if (status.changedFiles.length > 0) {
|
|
124
|
+
lines.push("Changed files:");
|
|
125
|
+
for (const file of status.changedFiles.slice(0, 10)) lines.push(`- ${file}`);
|
|
126
|
+
}
|
|
127
|
+
if (status.stale) lines.push(`Manual refresh: ${status.commands.update}`);
|
|
128
|
+
return lines.join("\n");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function formatNativeWikiBuildResult(result: NativeWikiBuildResult) {
|
|
132
|
+
return [
|
|
133
|
+
`Optional human wiki: ${result.mode}`,
|
|
134
|
+
`state: ${result.state.status}`,
|
|
135
|
+
`baseCommit: ${result.state.baseCommit ?? "unknown"}`,
|
|
136
|
+
`files: ${result.index.files.length}`,
|
|
137
|
+
`edges: ${result.graph.edges.length}`,
|
|
138
|
+
`dimensions: ${result.dimensions.files.length}`,
|
|
139
|
+
`docs: ${result.docs.join(", ")}`,
|
|
140
|
+
].join("\n");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function formatNativeWikiEvalReport(report: NativeWikiEvalReport) {
|
|
144
|
+
const lines = [
|
|
145
|
+
`Optional human wiki eval: ${report.native.status.state}`,
|
|
146
|
+
`generated docs: ${report.native.generatedDocs.count}`,
|
|
147
|
+
`project topic clusters: ${report.native.projectTopics.clusterCount}`,
|
|
148
|
+
`indexed files: ${report.native.sourceCoverage.indexedFiles}`,
|
|
149
|
+
`source-anchored files: ${report.native.sourceCoverage.anchoredFiles}`,
|
|
150
|
+
`orphan indexed files: ${report.native.sourceCoverage.orphanIndexedFiles}`,
|
|
151
|
+
];
|
|
152
|
+
if (report.native.queryScenarios.length > 0) {
|
|
153
|
+
lines.push("");
|
|
154
|
+
lines.push("Query scenarios:");
|
|
155
|
+
for (const scenario of report.native.queryScenarios) {
|
|
156
|
+
lines.push(`- ${scenario.query}: ${scenario.topFiles.slice(0, 3).join(", ") || "no matches"}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
lines.push("");
|
|
160
|
+
lines.push("Recommendations:");
|
|
161
|
+
for (const recommendation of report.recommendations) lines.push(`- ${recommendation}`);
|
|
162
|
+
return lines.join("\n");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function formatNativeWikiQueryResult(result: NativeWikiQueryResult) {
|
|
166
|
+
const lines = [`Optional human wiki query: ${result.query}`];
|
|
167
|
+
if (result.results.length === 0) {
|
|
168
|
+
lines.push("No matches. Try `gxpm wiki init` if the index is stale.");
|
|
169
|
+
return lines.join("\n");
|
|
170
|
+
}
|
|
171
|
+
lines.push("Source files:");
|
|
172
|
+
for (const r of result.results) {
|
|
173
|
+
lines.push(`- ${r.path}${r.line ? ` #L${r.line}` : ""}`);
|
|
174
|
+
}
|
|
175
|
+
if (result.suggestedDocs.length > 0) {
|
|
176
|
+
lines.push("Suggested human docs:");
|
|
177
|
+
for (const doc of result.suggestedDocs) lines.push(`- ${doc}`);
|
|
178
|
+
}
|
|
179
|
+
return lines.join("\n");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function formatNativeWikiIssueContext(result: NativeWikiIssueContext, artifactWritten: boolean) {
|
|
183
|
+
const lines = [
|
|
184
|
+
`Optional human wiki context: ${result.issueId}`,
|
|
185
|
+
`phase: ${result.phase} (current: ${result.currentPhase})`,
|
|
186
|
+
`query: ${result.query}`,
|
|
187
|
+
];
|
|
188
|
+
if (result.artifactsUsed.length > 0) {
|
|
189
|
+
lines.push("Artifacts used:");
|
|
190
|
+
for (const artifact of result.artifactsUsed) lines.push(`- ${artifact.type}`);
|
|
191
|
+
}
|
|
192
|
+
if (result.results.length === 0) {
|
|
193
|
+
lines.push("No source files matched. Try `gxpm wiki update` if humans need refreshed docs.");
|
|
194
|
+
} else {
|
|
195
|
+
lines.push("Source files:");
|
|
196
|
+
for (const r of result.results) {
|
|
197
|
+
lines.push(`- ${r.path}${r.line ? ` #L${r.line}` : ""}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (result.suggestedDocs.length > 0) {
|
|
201
|
+
lines.push("Suggested human docs:");
|
|
202
|
+
for (const doc of result.suggestedDocs) lines.push(`- ${doc}`);
|
|
203
|
+
}
|
|
204
|
+
if (artifactWritten) lines.push("Artifact written: wiki-context");
|
|
205
|
+
return lines.join("\n");
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function wikiQueryText(argv: string[]) {
|
|
209
|
+
const values: string[] = [];
|
|
210
|
+
for (let index = 2; index < argv.length; index++) {
|
|
211
|
+
const arg = argv[index];
|
|
212
|
+
if (arg === "--json") continue;
|
|
213
|
+
if (arg === "--limit") {
|
|
214
|
+
index++;
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (arg.startsWith("--")) {
|
|
218
|
+
const next = argv[index + 1];
|
|
219
|
+
if (next && !next.startsWith("--")) index++;
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
values.push(arg);
|
|
223
|
+
}
|
|
224
|
+
return values.join(" ").trim();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function assertNoUnexpectedWikiContextPositionals(argv: string[]) {
|
|
228
|
+
const optionsWithValues = new Set(["--phase", "--limit"]);
|
|
229
|
+
const flagOptions = new Set(["--json", "--write-artifact"]);
|
|
230
|
+
for (let index = 3; index < argv.length; index++) {
|
|
231
|
+
const arg = argv[index];
|
|
232
|
+
if (optionsWithValues.has(arg)) {
|
|
233
|
+
index++;
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (flagOptions.has(arg)) continue;
|
|
237
|
+
if (arg.startsWith("--")) {
|
|
238
|
+
throw new Error(`Unknown option for gxpm wiki context: ${arg}`);
|
|
239
|
+
}
|
|
240
|
+
throw new Error(WIKI_CONTEXT_USAGE);
|
|
241
|
+
}
|
|
242
|
+
}
|