@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,201 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { mkdirSync } from "node:fs";
|
|
4
|
+
import { discoverTemplates } from "./discover-skills";
|
|
5
|
+
import { getHostConfig } from "../hosts";
|
|
6
|
+
import type { HostConfig } from "../core/contracts/host";
|
|
7
|
+
import { PHASE_GATE_RULES } from "../core/phase-gates";
|
|
8
|
+
import { renderTemplate } from "../core/converters/template-renderer";
|
|
9
|
+
import { SkillParser, HostConverter, SkillWriter } from "../core/converters";
|
|
10
|
+
import { PresetResolver } from "../core/preset-system/preset-resolver";
|
|
11
|
+
|
|
12
|
+
export interface GenerateSkillDocsOptions {
|
|
13
|
+
root?: string;
|
|
14
|
+
host?: string;
|
|
15
|
+
dryRun?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const GENERATED_MARK = "<!-- AUTO-GENERATED from SKILL.md.tmpl - do not edit directly -->";
|
|
19
|
+
|
|
20
|
+
export function renderSkillContentForHost(
|
|
21
|
+
root: string,
|
|
22
|
+
host: HostConfig,
|
|
23
|
+
templateRelative: string,
|
|
24
|
+
references?: string[],
|
|
25
|
+
): string {
|
|
26
|
+
const templatePath = join(root, templateRelative);
|
|
27
|
+
const source = readFileSync(templatePath, "utf8");
|
|
28
|
+
|
|
29
|
+
// Static files (non-.tmpl) are returned as-is for backward compatibility
|
|
30
|
+
if (!templateRelative.endsWith(".tmpl")) {
|
|
31
|
+
return source;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Template files keep string-replacement behavior for backward compatibility
|
|
35
|
+
const rendered = renderTemplate(source, {
|
|
36
|
+
PREAMBLE: buildPreamble(root, host),
|
|
37
|
+
CANON: buildCanonInline(root),
|
|
38
|
+
ARTIFACT_READ_COMMANDS: buildArtifactReadCommands(),
|
|
39
|
+
PHASE_GATE_COMMANDS: buildPhaseGateCommands(),
|
|
40
|
+
PHASE_TRANSITION_SUMMARY: buildPhaseTransitionSummary(),
|
|
41
|
+
references: buildReferences(root, references),
|
|
42
|
+
});
|
|
43
|
+
return insertGeneratedMark(rendered);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function generateSkillDocs(options: GenerateSkillDocsOptions = {}): string[] {
|
|
47
|
+
const root = options.root ?? process.cwd();
|
|
48
|
+
const host = getHostConfig(options.host ?? "codex");
|
|
49
|
+
const outputs: string[] = [];
|
|
50
|
+
const stale: string[] = [];
|
|
51
|
+
|
|
52
|
+
// Load preset resolver for composition layer support
|
|
53
|
+
const resolver = new PresetResolver(root);
|
|
54
|
+
resolver.load();
|
|
55
|
+
|
|
56
|
+
for (const template of discoverTemplates(root)) {
|
|
57
|
+
const outputPath = join(root, template.output);
|
|
58
|
+
const generated = renderSkillContentForHost(root, host, template.tmpl, template.references);
|
|
59
|
+
|
|
60
|
+
// Apply Override > Preset > Core resolution
|
|
61
|
+
const resolved = resolver.resolve(template.output, generated);
|
|
62
|
+
const finalContent = resolved?.content ?? generated;
|
|
63
|
+
|
|
64
|
+
if (options.dryRun) {
|
|
65
|
+
const current = existsSync(outputPath) ? readFileSync(outputPath, "utf8") : "";
|
|
66
|
+
if (current !== finalContent) {
|
|
67
|
+
stale.push(template.output);
|
|
68
|
+
}
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
73
|
+
writeFileSync(outputPath, finalContent);
|
|
74
|
+
outputs.push(template.output);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (stale.length > 0) {
|
|
78
|
+
throw new Error(`Stale generated skill docs:\n${stale.map((path) => `- ${path}`).join("\n")}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return outputs;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function buildReferences(root: string, references?: string[]): Record<string, string> {
|
|
85
|
+
const result: Record<string, string> = {};
|
|
86
|
+
if (!references) return result;
|
|
87
|
+
for (const refPath of references) {
|
|
88
|
+
const name = refPath.replace(/^.*\//, "").replace(/\.md$/, "");
|
|
89
|
+
const content = readFileSync(join(root, refPath), "utf8");
|
|
90
|
+
result[name] = content;
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function insertGeneratedMark(content: string) {
|
|
96
|
+
const frontmatter = content.match(/^---\n[\s\S]*?\n---\n?/);
|
|
97
|
+
if (!frontmatter) {
|
|
98
|
+
return `${GENERATED_MARK}\n\n${content}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const head = frontmatter[0].trimEnd();
|
|
102
|
+
const body = content.slice(frontmatter[0].length).replace(/^\n+/, "");
|
|
103
|
+
return `${head}\n${GENERATED_MARK}\n\n${body}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function buildCanonInline(root: string): string {
|
|
107
|
+
const canonPath = join(root, "CANON.md");
|
|
108
|
+
if (!existsSync(canonPath)) {
|
|
109
|
+
return "";
|
|
110
|
+
}
|
|
111
|
+
const content = readFileSync(canonPath, "utf8");
|
|
112
|
+
return [
|
|
113
|
+
"## CANON(行为宪法)",
|
|
114
|
+
"",
|
|
115
|
+
"以下纪律内联自 gxpm 源码仓库 `CANON.md`,所有 gxpm 操作前必须遵守:",
|
|
116
|
+
"",
|
|
117
|
+
content.replace(/^# .*(\n|$)/, "").trim(),
|
|
118
|
+
].join("\n");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function buildPreamble(_root: string, host: ReturnType<typeof getHostConfig>) {
|
|
122
|
+
const envLines = host.usesEnvVars
|
|
123
|
+
? [
|
|
124
|
+
'GXPM_ROOT="${GXPM_ROOT:-$PWD}"',
|
|
125
|
+
`GXPM_STATE_DIR="\${GXPM_STATE_DIR:-$GXPM_ROOT/.gxpm}"`,
|
|
126
|
+
"export GXPM_ROOT GXPM_STATE_DIR",
|
|
127
|
+
]
|
|
128
|
+
: ['GXPM_ROOT="${GXPM_ROOT:-$PWD}"', "export GXPM_ROOT"];
|
|
129
|
+
|
|
130
|
+
return [
|
|
131
|
+
"## Host Preamble",
|
|
132
|
+
"",
|
|
133
|
+
`Target host: ${host.displayName}.`,
|
|
134
|
+
"",
|
|
135
|
+
"```bash",
|
|
136
|
+
...envLines,
|
|
137
|
+
"```",
|
|
138
|
+
].join("\n");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function buildArtifactReadCommands() {
|
|
142
|
+
return PHASE_GATE_RULES.map((rule) => `gxpm artifact read <issue-id> ${rule.requiredArtifact}`).join(
|
|
143
|
+
"\n",
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function buildPhaseGateCommands() {
|
|
148
|
+
return PHASE_GATE_RULES.map((rule) =>
|
|
149
|
+
[
|
|
150
|
+
`Before leaving \`${rule.fromPhase}\`, initialize \`${rule.requiredArtifact}\`:`,
|
|
151
|
+
"",
|
|
152
|
+
"```bash",
|
|
153
|
+
rule.command,
|
|
154
|
+
"```",
|
|
155
|
+
].join("\n"),
|
|
156
|
+
).join("\n\n");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function buildPhaseTransitionSummary() {
|
|
160
|
+
const gateSummary = PHASE_GATE_RULES.map(
|
|
161
|
+
(rule) =>
|
|
162
|
+
`\`${rule.fromPhase} -> ${rule.nextPhase}\` is blocked until \`${rule.requiredArtifact}\` exists.`,
|
|
163
|
+
).join(" ");
|
|
164
|
+
|
|
165
|
+
return [
|
|
166
|
+
"V0 phase transitions are strict.",
|
|
167
|
+
"Use `gxpm issue transition <issue-id> <next-phase>` only for the next phase in the phase map.",
|
|
168
|
+
gateSummary,
|
|
169
|
+
].join(" ");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function parseArgs(argv: string[]) {
|
|
173
|
+
const options: GenerateSkillDocsOptions = {};
|
|
174
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
175
|
+
const arg = argv[index];
|
|
176
|
+
if (arg === "--host") {
|
|
177
|
+
options.host = argv[++index];
|
|
178
|
+
} else if (arg === "--dry-run") {
|
|
179
|
+
options.dryRun = true;
|
|
180
|
+
} else if (arg === "--root") {
|
|
181
|
+
options.root = argv[++index];
|
|
182
|
+
} else {
|
|
183
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return options;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (import.meta.main) {
|
|
190
|
+
try {
|
|
191
|
+
const outputs = generateSkillDocs(parseArgs(Bun.argv.slice(2)));
|
|
192
|
+
if (outputs.length > 0) {
|
|
193
|
+
console.log(outputs.map((path) => `generated ${path}`).join("\n"));
|
|
194
|
+
} else {
|
|
195
|
+
console.log("skill docs are current");
|
|
196
|
+
}
|
|
197
|
+
} catch (error) {
|
|
198
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
|
|
6
|
+
/** Extract a cwd string from a parsed JSON object, checking top-level and payload.cwd. */
|
|
7
|
+
function extractCwd(parsed: unknown): string | null {
|
|
8
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
9
|
+
const obj = parsed as Record<string, unknown>;
|
|
10
|
+
if (typeof obj.cwd === "string") return obj.cwd;
|
|
11
|
+
if (obj.payload && typeof obj.payload === "object") {
|
|
12
|
+
const payload = obj.payload as Record<string, unknown>;
|
|
13
|
+
if (typeof payload.cwd === "string") return payload.cwd;
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface DiscoverEntry {
|
|
19
|
+
key: string;
|
|
20
|
+
repos: string[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface RunGlobalDiscoverInput {
|
|
24
|
+
home?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Cache for remote resolution to avoid repeated git subprocess calls per cwd. */
|
|
28
|
+
const remoteCache = new Map<string, string | null>();
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Resolve the primary git remote origin URL for a directory.
|
|
32
|
+
* Results are cached by cwd so each directory is queried at most once.
|
|
33
|
+
* Returns null if the directory is not a git repo or has no remote.
|
|
34
|
+
*/
|
|
35
|
+
function resolveRemote(cwd: string): string | null {
|
|
36
|
+
if (remoteCache.has(cwd)) {
|
|
37
|
+
return remoteCache.get(cwd)!;
|
|
38
|
+
}
|
|
39
|
+
let result: string | null = null;
|
|
40
|
+
try {
|
|
41
|
+
const url = execFileSync("git", ["remote", "get-url", "origin"], {
|
|
42
|
+
cwd,
|
|
43
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
44
|
+
})
|
|
45
|
+
.toString()
|
|
46
|
+
.trim();
|
|
47
|
+
result = url || null;
|
|
48
|
+
} catch {
|
|
49
|
+
result = null;
|
|
50
|
+
}
|
|
51
|
+
remoteCache.set(cwd, result);
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Read cwd values from a single file.
|
|
57
|
+
* Supports whole-file JSON and line-delimited JSON content.
|
|
58
|
+
*/
|
|
59
|
+
function readCwdsFromFile(filePath: string): string[] {
|
|
60
|
+
let raw: string;
|
|
61
|
+
try {
|
|
62
|
+
raw = readFileSync(filePath, "utf8");
|
|
63
|
+
} catch {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const cwds: string[] = [];
|
|
68
|
+
|
|
69
|
+
// Try whole-file JSON first; if it succeeds, skip line-by-line to avoid duplicates.
|
|
70
|
+
let isWholeFileJson = false;
|
|
71
|
+
try {
|
|
72
|
+
const parsed = JSON.parse(raw);
|
|
73
|
+
isWholeFileJson = true;
|
|
74
|
+
const cwd = extractCwd(parsed);
|
|
75
|
+
if (cwd) cwds.push(cwd);
|
|
76
|
+
} catch {
|
|
77
|
+
// not valid whole-file JSON; fall through to line-by-line parsing
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!isWholeFileJson) {
|
|
81
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
82
|
+
const trimmed = line.trim();
|
|
83
|
+
if (!trimmed) continue;
|
|
84
|
+
try {
|
|
85
|
+
const parsed = JSON.parse(trimmed);
|
|
86
|
+
const cwd = extractCwd(parsed);
|
|
87
|
+
if (cwd) cwds.push(cwd);
|
|
88
|
+
} catch {
|
|
89
|
+
// ignore malformed lines
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return cwds;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function listFiles(root: string, recursive: boolean): string[] {
|
|
98
|
+
if (!existsSync(root)) {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let names: string[];
|
|
103
|
+
try {
|
|
104
|
+
names = readdirSync(root).sort((a, b) => a.localeCompare(b));
|
|
105
|
+
} catch {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const files: string[] = [];
|
|
110
|
+
|
|
111
|
+
for (const name of names) {
|
|
112
|
+
const fullPath = join(root, name);
|
|
113
|
+
let stats;
|
|
114
|
+
try {
|
|
115
|
+
stats = statSync(fullPath);
|
|
116
|
+
} catch {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (stats.isFile()) {
|
|
121
|
+
files.push(fullPath);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (recursive && stats.isDirectory()) {
|
|
126
|
+
files.push(...listFiles(fullPath, true));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return files;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Read cwd entries from ~/.claude/projects.
|
|
135
|
+
* Supports both flat test fixtures and real one-level slug directories.
|
|
136
|
+
*/
|
|
137
|
+
function readClaudeCwdEntries(root: string): string[] {
|
|
138
|
+
if (!existsSync(root)) {
|
|
139
|
+
return [];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const files: string[] = [];
|
|
143
|
+
let names: string[];
|
|
144
|
+
try {
|
|
145
|
+
names = readdirSync(root).sort((a, b) => a.localeCompare(b));
|
|
146
|
+
} catch {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
for (const name of names) {
|
|
151
|
+
const fullPath = join(root, name);
|
|
152
|
+
let stats;
|
|
153
|
+
try {
|
|
154
|
+
stats = statSync(fullPath);
|
|
155
|
+
} catch {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (stats.isFile()) {
|
|
160
|
+
files.push(fullPath);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (stats.isDirectory()) {
|
|
165
|
+
files.push(...listFiles(fullPath, false));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return files.flatMap(readCwdsFromFile);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Read cwd entries from ~/.codex/sessions.
|
|
174
|
+
* Supports both flat test fixtures and real nested date directories.
|
|
175
|
+
*/
|
|
176
|
+
function readCodexCwdEntries(root: string): string[] {
|
|
177
|
+
return listFiles(root, true).flatMap(readCwdsFromFile);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Scan ~/.claude/projects and ~/.codex/sessions for session JSON files,
|
|
182
|
+
* extract cwd values, resolve git remotes, and deduplicate entries.
|
|
183
|
+
*/
|
|
184
|
+
export function runGlobalDiscover(input: RunGlobalDiscoverInput = {}): DiscoverEntry[] {
|
|
185
|
+
const home = input.home ?? homedir();
|
|
186
|
+
|
|
187
|
+
const claudeRoot = join(home, ".claude", "projects");
|
|
188
|
+
const codexRoot = join(home, ".codex", "sessions");
|
|
189
|
+
|
|
190
|
+
const allCwds = [
|
|
191
|
+
...readClaudeCwdEntries(claudeRoot),
|
|
192
|
+
...readCodexCwdEntries(codexRoot),
|
|
193
|
+
];
|
|
194
|
+
|
|
195
|
+
// Map from key -> set of cwd paths
|
|
196
|
+
const keyToRepos = new Map<string, Set<string>>();
|
|
197
|
+
|
|
198
|
+
for (const cwd of allCwds) {
|
|
199
|
+
const remote = resolveRemote(cwd);
|
|
200
|
+
const key = remote ? `remote:${remote}` : `cwd:${cwd}`;
|
|
201
|
+
|
|
202
|
+
if (!keyToRepos.has(key)) {
|
|
203
|
+
keyToRepos.set(key, new Set());
|
|
204
|
+
}
|
|
205
|
+
keyToRepos.get(key)!.add(cwd);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const result: DiscoverEntry[] = [];
|
|
209
|
+
for (const [key, repoSet] of keyToRepos) {
|
|
210
|
+
result.push({
|
|
211
|
+
key,
|
|
212
|
+
repos: Array.from(repoSet).sort((a, b) => a.localeCompare(b)),
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return result.sort((a, b) => a.key.localeCompare(b.key));
|
|
217
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
export interface GovernanceCheckOptions {
|
|
5
|
+
root?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const REQUIRED_DOCS = [
|
|
9
|
+
"CANON.md",
|
|
10
|
+
"AGENTS.md",
|
|
11
|
+
"CLAUDE.md",
|
|
12
|
+
"docs/governance/development-contract.md",
|
|
13
|
+
"docs/governance/template-authoring.md",
|
|
14
|
+
"docs/governance/host-adapter.md",
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
export function validateGovernanceDocs(options: GovernanceCheckOptions = {}): string[] {
|
|
18
|
+
const root = options.root ?? process.cwd();
|
|
19
|
+
const errors: string[] = [];
|
|
20
|
+
|
|
21
|
+
for (const doc of REQUIRED_DOCS) {
|
|
22
|
+
if (!existsSync(join(root, doc))) {
|
|
23
|
+
errors.push(`missing governance doc: ${doc}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (errors.length > 0) {
|
|
28
|
+
return errors;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const agents = readText(root, "AGENTS.md");
|
|
32
|
+
const claude = readText(root, "CLAUDE.md");
|
|
33
|
+
|
|
34
|
+
if (lineCount(agents) > 150) {
|
|
35
|
+
errors.push("AGENTS.md should stay under 150 lines");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (lineCount(claude) > 80) {
|
|
39
|
+
errors.push("CLAUDE.md should stay a thin bootstrap under 80 lines");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!agents.includes("CANON.md")) {
|
|
43
|
+
errors.push("AGENTS.md must reference CANON.md");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const canon = readText(root, "CANON.md");
|
|
47
|
+
for (const heading of ["## 1. 真值优先", "## 9. 安全门控", "## 10. 失败归因"]) {
|
|
48
|
+
if (!canon.includes(heading)) {
|
|
49
|
+
errors.push(`CANON.md missing boundary heading: ${heading}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!claude.includes("AGENTS.md")) {
|
|
54
|
+
errors.push("CLAUDE.md must point back to AGENTS.md");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return errors;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function readText(root: string, path: string) {
|
|
61
|
+
return readFileSync(join(root, path), "utf8");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function lineCount(content: string) {
|
|
65
|
+
return content.trimEnd().split("\n").length;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (import.meta.main) {
|
|
69
|
+
const errors = validateGovernanceDocs();
|
|
70
|
+
if (errors.length > 0) {
|
|
71
|
+
console.error(errors.map((error) => `governance: ${error}`).join("\n"));
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
console.log("governance docs check passed");
|
|
75
|
+
}
|