cleargate 0.5.0 → 0.6.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/dist/MANIFEST.json +30 -16
- package/dist/cli.cjs +486 -51
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +481 -47
- package/dist/cli.js.map +1 -1
- package/dist/templates/cleargate-planning/.claude/agents/architect.md +24 -0
- package/dist/templates/cleargate-planning/.claude/agents/developer.md +24 -0
- package/dist/templates/cleargate-planning/.claude/agents/reporter.md +74 -0
- package/dist/templates/cleargate-planning/.claude/hooks/pre-edit-gate.sh +162 -0
- package/dist/templates/cleargate-planning/.claude/hooks/session-start.sh +10 -7
- package/dist/templates/cleargate-planning/.claude/hooks/stamp-and-gate.sh +9 -8
- package/dist/templates/cleargate-planning/.claude/hooks/token-ledger.sh +36 -13
- package/dist/templates/cleargate-planning/.claude/settings.json +9 -0
- package/dist/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +55 -0
- package/dist/templates/cleargate-planning/.cleargate/knowledge/readiness-gates.md +7 -7
- package/dist/templates/cleargate-planning/.cleargate/scripts/assert_story_files.mjs +137 -40
- package/dist/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +93 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/constants.mjs +8 -4
- package/dist/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +9 -1
- package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +74 -0
- package/dist/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +65 -1
- package/dist/templates/cleargate-planning/.cleargate/scripts/state.schema.json +31 -8
- package/dist/templates/cleargate-planning/.cleargate/scripts/update_state.mjs +93 -8
- package/dist/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +19 -4
- package/dist/templates/cleargate-planning/.cleargate/templates/hotfix.md +58 -0
- package/dist/templates/cleargate-planning/.cleargate/templates/sprint_report.md +32 -2
- package/dist/templates/cleargate-planning/.cleargate/templates/story.md +3 -1
- package/dist/templates/cleargate-planning/CLAUDE.md +1 -1
- package/dist/templates/cleargate-planning/MANIFEST.json +30 -16
- package/package.json +1 -1
- package/templates/cleargate-planning/.claude/agents/architect.md +24 -0
- package/templates/cleargate-planning/.claude/agents/developer.md +24 -0
- package/templates/cleargate-planning/.claude/agents/reporter.md +74 -0
- package/templates/cleargate-planning/.claude/hooks/pre-edit-gate.sh +162 -0
- package/templates/cleargate-planning/.claude/hooks/session-start.sh +10 -7
- package/templates/cleargate-planning/.claude/hooks/stamp-and-gate.sh +9 -8
- package/templates/cleargate-planning/.claude/hooks/token-ledger.sh +36 -13
- package/templates/cleargate-planning/.claude/settings.json +9 -0
- package/templates/cleargate-planning/.cleargate/knowledge/cleargate-protocol.md +55 -0
- package/templates/cleargate-planning/.cleargate/knowledge/readiness-gates.md +7 -7
- package/templates/cleargate-planning/.cleargate/scripts/assert_story_files.mjs +137 -40
- package/templates/cleargate-planning/.cleargate/scripts/close_sprint.mjs +93 -0
- package/templates/cleargate-planning/.cleargate/scripts/constants.mjs +8 -4
- package/templates/cleargate-planning/.cleargate/scripts/init_sprint.mjs +9 -1
- package/templates/cleargate-planning/.cleargate/scripts/pre_gate_common.sh +74 -0
- package/templates/cleargate-planning/.cleargate/scripts/pre_gate_runner.sh +65 -1
- package/templates/cleargate-planning/.cleargate/scripts/state.schema.json +31 -8
- package/templates/cleargate-planning/.cleargate/scripts/update_state.mjs +93 -8
- package/templates/cleargate-planning/.cleargate/templates/Sprint Plan Template.md +19 -4
- package/templates/cleargate-planning/.cleargate/templates/hotfix.md +58 -0
- package/templates/cleargate-planning/.cleargate/templates/sprint_report.md +32 -2
- package/templates/cleargate-planning/.cleargate/templates/story.md +3 -1
- package/templates/cleargate-planning/CLAUDE.md +1 -1
- package/templates/cleargate-planning/MANIFEST.json +30 -16
package/dist/cli.cjs
CHANGED
|
@@ -627,7 +627,7 @@ var import_commander = require("commander");
|
|
|
627
627
|
// package.json
|
|
628
628
|
var package_default = {
|
|
629
629
|
name: "cleargate",
|
|
630
|
-
version: "0.
|
|
630
|
+
version: "0.6.0",
|
|
631
631
|
private: false,
|
|
632
632
|
type: "module",
|
|
633
633
|
description: "Planning framework for Claude Code agents \u2014 sprint/epic/story protocol, four-agent loop (architect/developer/qa/reporter), Karpathy-style awareness wiki.",
|
|
@@ -1900,11 +1900,17 @@ init_cjs_shims();
|
|
|
1900
1900
|
var fs15 = __toESM(require("fs"), 1);
|
|
1901
1901
|
var path16 = __toESM(require("path"), 1);
|
|
1902
1902
|
var import_node_url5 = require("url");
|
|
1903
|
+
var import_node_child_process3 = require("child_process");
|
|
1903
1904
|
|
|
1904
1905
|
// src/init/copy-payload.ts
|
|
1905
1906
|
init_cjs_shims();
|
|
1906
1907
|
var fs7 = __toESM(require("fs"), 1);
|
|
1907
1908
|
var path7 = __toESM(require("path"), 1);
|
|
1909
|
+
var PIN_PLACEHOLDER = "__CLEARGATE_VERSION__";
|
|
1910
|
+
var HOOK_FILES_WITH_PIN = /* @__PURE__ */ new Set([
|
|
1911
|
+
".claude/hooks/stamp-and-gate.sh",
|
|
1912
|
+
".claude/hooks/session-start.sh"
|
|
1913
|
+
]);
|
|
1908
1914
|
function listFilesRecursive(dir) {
|
|
1909
1915
|
const results = [];
|
|
1910
1916
|
function walk(current, rel) {
|
|
@@ -1932,10 +1938,15 @@ function copyPayload(payloadDir, targetCwd, opts) {
|
|
|
1932
1938
|
const srcPath = path7.join(payloadDir, relPath);
|
|
1933
1939
|
const dstPath = path7.join(targetCwd, relPath);
|
|
1934
1940
|
fs7.mkdirSync(path7.dirname(dstPath), { recursive: true });
|
|
1935
|
-
|
|
1941
|
+
let srcContent = fs7.readFileSync(srcPath);
|
|
1942
|
+
if (opts.pinVersion && HOOK_FILES_WITH_PIN.has(relPath)) {
|
|
1943
|
+
const text = srcContent.toString("utf8").replaceAll(PIN_PLACEHOLDER, opts.pinVersion);
|
|
1944
|
+
srcContent = text;
|
|
1945
|
+
}
|
|
1946
|
+
const srcBuffer = typeof srcContent === "string" ? Buffer.from(srcContent, "utf8") : srcContent;
|
|
1936
1947
|
if (fs7.existsSync(dstPath)) {
|
|
1937
1948
|
const dstContent = fs7.readFileSync(dstPath);
|
|
1938
|
-
if (
|
|
1949
|
+
if (srcBuffer.equals(dstContent)) {
|
|
1939
1950
|
report.skipped++;
|
|
1940
1951
|
report.actions.push({ action: "skipped", relPath });
|
|
1941
1952
|
continue;
|
|
@@ -1945,11 +1956,11 @@ function copyPayload(payloadDir, targetCwd, opts) {
|
|
|
1945
1956
|
report.actions.push({ action: "skipped", relPath });
|
|
1946
1957
|
continue;
|
|
1947
1958
|
}
|
|
1948
|
-
fs7.writeFileSync(dstPath,
|
|
1959
|
+
fs7.writeFileSync(dstPath, srcBuffer);
|
|
1949
1960
|
report.overwritten++;
|
|
1950
1961
|
report.actions.push({ action: "overwritten", relPath });
|
|
1951
1962
|
} else {
|
|
1952
|
-
fs7.writeFileSync(dstPath,
|
|
1963
|
+
fs7.writeFileSync(dstPath, srcBuffer);
|
|
1953
1964
|
report.created++;
|
|
1954
1965
|
report.actions.push({ action: "created", relPath });
|
|
1955
1966
|
}
|
|
@@ -2798,7 +2809,7 @@ init_cjs_shims();
|
|
|
2798
2809
|
var readline3 = __toESM(require("readline"), 1);
|
|
2799
2810
|
async function promptYesNo(question, defaultYes, opts) {
|
|
2800
2811
|
const stdoutFn = opts?.stdout ?? ((s) => process.stdout.write(s));
|
|
2801
|
-
stdoutFn(question + "
|
|
2812
|
+
stdoutFn(question + " ");
|
|
2802
2813
|
const inputStream = opts?.stdin ?? process.stdin;
|
|
2803
2814
|
return new Promise((resolve14) => {
|
|
2804
2815
|
const rl = readline3.createInterface({
|
|
@@ -2829,7 +2840,7 @@ async function promptYesNo(question, defaultYes, opts) {
|
|
|
2829
2840
|
}
|
|
2830
2841
|
async function promptEmail(question, defaultValue, opts) {
|
|
2831
2842
|
const stdoutFn = opts?.stdout ?? ((s) => process.stdout.write(s));
|
|
2832
|
-
stdoutFn(question + "
|
|
2843
|
+
stdoutFn(question + " ");
|
|
2833
2844
|
const inputStream = opts?.stdin ?? process.stdin;
|
|
2834
2845
|
return new Promise((resolve14) => {
|
|
2835
2846
|
const rl = readline3.createInterface({
|
|
@@ -2922,6 +2933,17 @@ function resolveIdentity(projectRoot, opts = {}) {
|
|
|
2922
2933
|
// src/commands/init.ts
|
|
2923
2934
|
var HOOK_ADDITION = {
|
|
2924
2935
|
hooks: {
|
|
2936
|
+
PreToolUse: [
|
|
2937
|
+
{
|
|
2938
|
+
matcher: "Edit|Write",
|
|
2939
|
+
hooks: [
|
|
2940
|
+
{
|
|
2941
|
+
type: "command",
|
|
2942
|
+
command: "${CLAUDE_PROJECT_DIR}/.claude/hooks/pre-edit-gate.sh"
|
|
2943
|
+
}
|
|
2944
|
+
]
|
|
2945
|
+
}
|
|
2946
|
+
],
|
|
2925
2947
|
PostToolUse: [
|
|
2926
2948
|
{
|
|
2927
2949
|
matcher: "Edit|Write",
|
|
@@ -2958,6 +2980,17 @@ function writeAtomic(filePath, content) {
|
|
|
2958
2980
|
fs15.writeFileSync(tmpPath, content, "utf8");
|
|
2959
2981
|
fs15.renameSync(tmpPath, filePath);
|
|
2960
2982
|
}
|
|
2983
|
+
function readPackageVersion(packageJsonPath) {
|
|
2984
|
+
try {
|
|
2985
|
+
const raw = fs15.readFileSync(packageJsonPath, "utf8");
|
|
2986
|
+
const pkg = JSON.parse(raw);
|
|
2987
|
+
if (typeof pkg.version === "string" && pkg.version.length > 0) {
|
|
2988
|
+
return pkg.version;
|
|
2989
|
+
}
|
|
2990
|
+
} catch {
|
|
2991
|
+
}
|
|
2992
|
+
return null;
|
|
2993
|
+
}
|
|
2961
2994
|
async function initHandler(opts = {}) {
|
|
2962
2995
|
const cwd = opts.cwd ?? process.cwd();
|
|
2963
2996
|
const force = opts.force ?? false;
|
|
@@ -2968,6 +3001,7 @@ async function initHandler(opts = {}) {
|
|
|
2968
3001
|
const runWikiBuild = opts.runWikiBuild ?? wikiBuildHandler;
|
|
2969
3002
|
const promptYesNoFn = opts.promptYesNo ?? promptYesNo;
|
|
2970
3003
|
const promptEmailFn = opts.promptEmail ?? promptEmail;
|
|
3004
|
+
const spawnSyncFn = opts.spawnSyncFn ?? import_node_child_process3.spawnSync;
|
|
2971
3005
|
if (!fs15.existsSync(cwd)) {
|
|
2972
3006
|
stderr(`[cleargate init] ERROR: target directory does not exist: ${cwd}
|
|
2973
3007
|
`);
|
|
@@ -3029,7 +3063,14 @@ async function initHandler(opts = {}) {
|
|
|
3029
3063
|
}
|
|
3030
3064
|
}
|
|
3031
3065
|
}
|
|
3032
|
-
|
|
3066
|
+
let pinVersion;
|
|
3067
|
+
if (opts.pin) {
|
|
3068
|
+
pinVersion = opts.pin;
|
|
3069
|
+
} else {
|
|
3070
|
+
const payloadParent = path16.resolve(payloadDir, "..", "..");
|
|
3071
|
+
pinVersion = readPackageVersion(path16.join(payloadParent, "package.json")) ?? readPackageVersion(path16.join(path16.dirname((0, import_node_url5.fileURLToPath)(importMetaUrl)), "..", "package.json")) ?? "latest";
|
|
3072
|
+
}
|
|
3073
|
+
const copyReport = copyPayload(payloadDir, cwd, { force, pinVersion });
|
|
3033
3074
|
for (const action of copyReport.actions) {
|
|
3034
3075
|
const verb = action.action === "created" ? "Created" : action.action === "overwritten" ? "Overwritten" : "Skipped (exists)";
|
|
3035
3076
|
stdout(`[cleargate init] ${verb} ${action.relPath}
|
|
@@ -3110,6 +3151,47 @@ async function initHandler(opts = {}) {
|
|
|
3110
3151
|
`);
|
|
3111
3152
|
}
|
|
3112
3153
|
}
|
|
3154
|
+
{
|
|
3155
|
+
const distCliPath = path16.join(cwd, "cleargate-cli", "dist", "cli.js");
|
|
3156
|
+
let branch = null;
|
|
3157
|
+
let branchLabel = "";
|
|
3158
|
+
if (fs15.existsSync(distCliPath)) {
|
|
3159
|
+
branch = { cmd: "node", args: [distCliPath, "--version"] };
|
|
3160
|
+
branchLabel = `local dist (${distCliPath})`;
|
|
3161
|
+
} else {
|
|
3162
|
+
const whichResult = spawnSyncFn("command", ["-v", "cleargate"], {
|
|
3163
|
+
shell: true,
|
|
3164
|
+
encoding: "utf8",
|
|
3165
|
+
timeout: 3e3
|
|
3166
|
+
});
|
|
3167
|
+
if (whichResult.status === 0) {
|
|
3168
|
+
branch = { cmd: "cleargate", args: ["--version"] };
|
|
3169
|
+
branchLabel = "PATH (global install)";
|
|
3170
|
+
} else {
|
|
3171
|
+
branch = { cmd: "npx", args: ["-y", `cleargate@${pinVersion}`, "--version"] };
|
|
3172
|
+
branchLabel = `npx cleargate@${pinVersion} (cold-start ~600ms first call)`;
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
if (branch !== null) {
|
|
3176
|
+
const probeResult = spawnSyncFn(branch.cmd, branch.args, {
|
|
3177
|
+
encoding: "utf8",
|
|
3178
|
+
timeout: 15e3
|
|
3179
|
+
});
|
|
3180
|
+
if (probeResult.status === 0) {
|
|
3181
|
+
stdout(`[cleargate init] \u{1F7E2} cleargate CLI resolved via ${branchLabel}
|
|
3182
|
+
`);
|
|
3183
|
+
} else {
|
|
3184
|
+
stdout(
|
|
3185
|
+
`[cleargate init] \u{1F534} cleargate CLI: not resolvable \u2014 hooks will no-op.
|
|
3186
|
+
[cleargate init] Attempted: ${branchLabel}
|
|
3187
|
+
[cleargate init] Fix: npm i -g cleargate@${pinVersion} or npx cleargate@${pinVersion} doctor
|
|
3188
|
+
`
|
|
3189
|
+
);
|
|
3190
|
+
exit(1);
|
|
3191
|
+
return;
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3194
|
+
}
|
|
3113
3195
|
const existingParticipant = readParticipant(cwd);
|
|
3114
3196
|
if (existingParticipant === null) {
|
|
3115
3197
|
const identityOpts = opts.identityOpts ?? {};
|
|
@@ -3127,8 +3209,10 @@ async function initHandler(opts = {}) {
|
|
|
3127
3209
|
stdout(`[cleargate init] Participant identity: ${finalEmail} (inferred)
|
|
3128
3210
|
`);
|
|
3129
3211
|
} else {
|
|
3130
|
-
const
|
|
3131
|
-
const
|
|
3212
|
+
const isNoreply = gitEmail !== null && /@users\.noreply\.github\.com$/i.test(gitEmail);
|
|
3213
|
+
const defaultEmail = gitEmail !== null && !isNoreply ? gitEmail : "user@localhost";
|
|
3214
|
+
stdout("\n");
|
|
3215
|
+
const question = `Participant email (press Enter for default) [${defaultEmail}]:`;
|
|
3132
3216
|
const answer = await promptEmailFn(question, defaultEmail);
|
|
3133
3217
|
await writeParticipant(cwd, answer, "prompted", now);
|
|
3134
3218
|
stdout(`[cleargate init] Participant identity: ${answer} (prompted)
|
|
@@ -3146,7 +3230,7 @@ async function initHandler(opts = {}) {
|
|
|
3146
3230
|
init_cjs_shims();
|
|
3147
3231
|
var fs16 = __toESM(require("fs"), 1);
|
|
3148
3232
|
var path17 = __toESM(require("path"), 1);
|
|
3149
|
-
var
|
|
3233
|
+
var import_node_child_process4 = require("child_process");
|
|
3150
3234
|
var EXCLUDED_SUFFIXES2 = [
|
|
3151
3235
|
".cleargate/knowledge/",
|
|
3152
3236
|
".cleargate/templates/",
|
|
@@ -3298,7 +3382,7 @@ function checkContentUnchanged(absRawPath, sha, relRawPath, gitRunner) {
|
|
|
3298
3382
|
}
|
|
3299
3383
|
}
|
|
3300
3384
|
function defaultGitRunner(cmd, args) {
|
|
3301
|
-
const result = (0,
|
|
3385
|
+
const result = (0, import_node_child_process4.spawnSync)(cmd, args, { encoding: "utf8" });
|
|
3302
3386
|
if (result.status !== 0) return "\0__NONZERO__";
|
|
3303
3387
|
return result.stdout ?? "";
|
|
3304
3388
|
}
|
|
@@ -3550,7 +3634,7 @@ function loadWikiPages(wikiRoot) {
|
|
|
3550
3634
|
init_cjs_shims();
|
|
3551
3635
|
var fs18 = __toESM(require("fs"), 1);
|
|
3552
3636
|
var path19 = __toESM(require("path"), 1);
|
|
3553
|
-
var
|
|
3637
|
+
var import_node_child_process5 = require("child_process");
|
|
3554
3638
|
var import_js_yaml3 = __toESM(require("js-yaml"), 1);
|
|
3555
3639
|
|
|
3556
3640
|
// src/lib/work-item-type.ts
|
|
@@ -3646,7 +3730,7 @@ function checkStaleCommit(page, repoRoot, gitRunner) {
|
|
|
3646
3730
|
if (gitRunner) {
|
|
3647
3731
|
currentSha = gitRunner("git", ["log", "-1", "--format=%H", "--", rawPath]).trim();
|
|
3648
3732
|
} else {
|
|
3649
|
-
const result = (0,
|
|
3733
|
+
const result = (0, import_node_child_process5.spawnSync)("git", ["log", "-1", "--format=%H", "--", rawPath], {
|
|
3650
3734
|
encoding: "utf8",
|
|
3651
3735
|
cwd: repoRoot
|
|
3652
3736
|
});
|
|
@@ -4391,6 +4475,7 @@ function applyStatusFix(rawText, newStatus) {
|
|
|
4391
4475
|
init_cjs_shims();
|
|
4392
4476
|
var fs22 = __toESM(require("fs"), 1);
|
|
4393
4477
|
var path24 = __toESM(require("path"), 1);
|
|
4478
|
+
var import_node_child_process6 = require("child_process");
|
|
4394
4479
|
|
|
4395
4480
|
// src/lib/pricing.ts
|
|
4396
4481
|
init_cjs_shims();
|
|
@@ -4440,6 +4525,7 @@ function selectMode(flags) {
|
|
|
4440
4525
|
if (flags.checkScaffold) modes.push("check-scaffold");
|
|
4441
4526
|
if (flags.sessionStart) modes.push("session-start");
|
|
4442
4527
|
if (flags.pricing) modes.push("pricing");
|
|
4528
|
+
if (flags.canEdit) modes.push("can-edit");
|
|
4443
4529
|
if (modes.length > 1) {
|
|
4444
4530
|
throw new Error(
|
|
4445
4531
|
`cleargate doctor: mutually exclusive flags set: ${modes.join(", ")}. Use only one mode flag at a time.`
|
|
@@ -4464,7 +4550,18 @@ function parseHookLogLine(line) {
|
|
|
4464
4550
|
file: m[5].trim()
|
|
4465
4551
|
};
|
|
4466
4552
|
}
|
|
4467
|
-
function runHookHealth(stdout, cwd, now) {
|
|
4553
|
+
function runHookHealth(stdout, cwd, now, outcome) {
|
|
4554
|
+
const cleargateDir = path24.join(cwd, ".cleargate");
|
|
4555
|
+
if (!fs22.existsSync(cleargateDir)) {
|
|
4556
|
+
stdout("cleargate misconfigured: no .cleargate/ found. Run: cleargate init");
|
|
4557
|
+
if (outcome) outcome.configError = true;
|
|
4558
|
+
return;
|
|
4559
|
+
}
|
|
4560
|
+
const manifestPath = path24.join(cwd, "cleargate-planning", "MANIFEST.json");
|
|
4561
|
+
if (!fs22.existsSync(manifestPath)) {
|
|
4562
|
+
stdout(`cleargate misconfigured: cleargate-planning/MANIFEST.json not found. Run: cleargate init`);
|
|
4563
|
+
if (outcome) outcome.configError = true;
|
|
4564
|
+
}
|
|
4468
4565
|
const settingsPath = path24.join(cwd, ".claude", "settings.json");
|
|
4469
4566
|
if (!fs22.existsSync(settingsPath)) {
|
|
4470
4567
|
stdout("[doctor] No .claude/settings.json found \u2014 hook config unavailable.");
|
|
@@ -4601,7 +4698,57 @@ function parseCachedGateResult2(raw) {
|
|
|
4601
4698
|
failing_criteria: parsed.failing_criteria ?? []
|
|
4602
4699
|
};
|
|
4603
4700
|
}
|
|
4604
|
-
|
|
4701
|
+
function emitResolverStatusLine(cwd, stdout) {
|
|
4702
|
+
const distCliPath = path24.join(cwd, "cleargate-cli", "dist", "cli.js");
|
|
4703
|
+
if (fs22.existsSync(distCliPath)) {
|
|
4704
|
+
stdout(`cleargate CLI: local dist \u2014 ${distCliPath}`);
|
|
4705
|
+
return;
|
|
4706
|
+
}
|
|
4707
|
+
const whichResult = (0, import_node_child_process6.spawnSync)("command", ["-v", "cleargate"], {
|
|
4708
|
+
shell: true,
|
|
4709
|
+
encoding: "utf8",
|
|
4710
|
+
timeout: 3e3
|
|
4711
|
+
});
|
|
4712
|
+
if (whichResult.status === 0) {
|
|
4713
|
+
stdout("cleargate CLI: PATH (global install) \u2014 cleargate");
|
|
4714
|
+
return;
|
|
4715
|
+
}
|
|
4716
|
+
let pinVersion = "unknown";
|
|
4717
|
+
const hookPath = path24.join(cwd, ".claude", "hooks", "stamp-and-gate.sh");
|
|
4718
|
+
if (fs22.existsSync(hookPath)) {
|
|
4719
|
+
try {
|
|
4720
|
+
const hookContent = fs22.readFileSync(hookPath, "utf-8");
|
|
4721
|
+
const pinMatch = hookContent.match(/^#\s*cleargate-pin:\s*(\S+)\s*$/m);
|
|
4722
|
+
if (pinMatch?.[1]) {
|
|
4723
|
+
pinVersion = pinMatch[1];
|
|
4724
|
+
} else {
|
|
4725
|
+
const npxMatch = hookContent.match(/@cleargate\/cli@([^\s"']+)/);
|
|
4726
|
+
if (npxMatch?.[1]) pinVersion = npxMatch[1];
|
|
4727
|
+
}
|
|
4728
|
+
} catch {
|
|
4729
|
+
}
|
|
4730
|
+
}
|
|
4731
|
+
if (pinVersion === "unknown") {
|
|
4732
|
+
stdout("cleargate CLI: \u{1F534} not resolvable \u2014 hooks will no-op. Fix: npm i -g cleargate or npx cleargate doctor");
|
|
4733
|
+
} else {
|
|
4734
|
+
stdout(`cleargate CLI: npx @cleargate/cli@${pinVersion} (cold-start ~600ms first call)`);
|
|
4735
|
+
}
|
|
4736
|
+
}
|
|
4737
|
+
var PLANNING_FIRST_REMINDER = `Triage first, draft second:
|
|
4738
|
+
Before any Edit/Write that creates user-facing code, you must:
|
|
4739
|
+
(1) classify the request (Epic / Story / CR / Bug),
|
|
4740
|
+
(2) draft a work item under .cleargate/delivery/pending-sync/ from .cleargate/templates/,
|
|
4741
|
+
(3) halt at Gate 1 (Proposal approval) for human sign-off.
|
|
4742
|
+
Bypass this only if the user has explicitly waived planning in this conversation.`;
|
|
4743
|
+
async function runSessionStart(cwd, stdout, outcome) {
|
|
4744
|
+
const resolverLines = [];
|
|
4745
|
+
emitResolverStatusLine(cwd, (line) => {
|
|
4746
|
+
stdout(line);
|
|
4747
|
+
resolverLines.push(line);
|
|
4748
|
+
});
|
|
4749
|
+
if (outcome && resolverLines.some((l) => l.includes("\u{1F534}"))) {
|
|
4750
|
+
outcome.configError = true;
|
|
4751
|
+
}
|
|
4605
4752
|
const pendingSyncDir = path24.join(cwd, ".cleargate", "delivery", "pending-sync");
|
|
4606
4753
|
let files;
|
|
4607
4754
|
try {
|
|
@@ -4610,6 +4757,7 @@ async function runSessionStart(cwd, stdout) {
|
|
|
4610
4757
|
return;
|
|
4611
4758
|
}
|
|
4612
4759
|
const blocked = [];
|
|
4760
|
+
let hasApprovedStory = false;
|
|
4613
4761
|
for (const filePath of files) {
|
|
4614
4762
|
let raw;
|
|
4615
4763
|
try {
|
|
@@ -4624,6 +4772,9 @@ async function runSessionStart(cwd, stdout) {
|
|
|
4624
4772
|
} catch {
|
|
4625
4773
|
continue;
|
|
4626
4774
|
}
|
|
4775
|
+
if (fm["approved"] === true) {
|
|
4776
|
+
hasApprovedStory = true;
|
|
4777
|
+
}
|
|
4627
4778
|
const gate2 = parseCachedGateResult2(fm["cached_gate_result"]);
|
|
4628
4779
|
if (!gate2 || gate2.pass !== false) continue;
|
|
4629
4780
|
const idKeys = ["story_id", "epic_id", "proposal_id", "cr_id", "bug_id", "sprint_id"];
|
|
@@ -4641,9 +4792,19 @@ async function runSessionStart(cwd, stdout) {
|
|
|
4641
4792
|
const firstCriterionId = gate2.failing_criteria.length > 0 ? gate2.failing_criteria[0]?.id ?? "" : "";
|
|
4642
4793
|
blocked.push({ id: itemId, firstCriterionId });
|
|
4643
4794
|
}
|
|
4795
|
+
const activesentinel = path24.join(cwd, ".cleargate", "sprint-runs", ".active");
|
|
4796
|
+
const sprintActive = fs22.existsSync(activesentinel);
|
|
4797
|
+
const shouldRemind = !hasApprovedStory && !sprintActive;
|
|
4798
|
+
if (shouldRemind) {
|
|
4799
|
+
stdout(PLANNING_FIRST_REMINDER);
|
|
4800
|
+
if (blocked.length > 0) {
|
|
4801
|
+
stdout("");
|
|
4802
|
+
}
|
|
4803
|
+
}
|
|
4644
4804
|
if (blocked.length === 0) {
|
|
4645
4805
|
return;
|
|
4646
4806
|
}
|
|
4807
|
+
if (outcome) outcome.blocker = true;
|
|
4647
4808
|
const overflow = blocked.length > SESSION_START_MAX_ITEMS ? blocked.length - SESSION_START_MAX_ITEMS : 0;
|
|
4648
4809
|
const visible = blocked.slice(0, SESSION_START_MAX_ITEMS);
|
|
4649
4810
|
const lines = [`${blocked.length} items blocked:`];
|
|
@@ -4660,10 +4821,11 @@ async function runSessionStart(cwd, stdout) {
|
|
|
4660
4821
|
}
|
|
4661
4822
|
stdout(output);
|
|
4662
4823
|
}
|
|
4663
|
-
async function runPricing(filePath, cwd, stdout, stderr, exit) {
|
|
4824
|
+
async function runPricing(filePath, cwd, stdout, stderr, exit, outcome) {
|
|
4664
4825
|
if (!filePath) {
|
|
4665
4826
|
stderr("cleargate doctor --pricing: missing <file> argument");
|
|
4666
|
-
|
|
4827
|
+
if (outcome) outcome.configError = true;
|
|
4828
|
+
exit(2);
|
|
4667
4829
|
return;
|
|
4668
4830
|
}
|
|
4669
4831
|
const absPath = path24.isAbsolute(filePath) ? filePath : path24.resolve(cwd, filePath);
|
|
@@ -4672,12 +4834,14 @@ async function runPricing(filePath, cwd, stdout, stderr, exit) {
|
|
|
4672
4834
|
raw = fs22.readFileSync(absPath, "utf-8");
|
|
4673
4835
|
} catch {
|
|
4674
4836
|
stderr(`cleargate doctor --pricing: cannot read file: ${absPath}`);
|
|
4675
|
-
|
|
4837
|
+
if (outcome) outcome.configError = true;
|
|
4838
|
+
exit(2);
|
|
4676
4839
|
return;
|
|
4677
4840
|
}
|
|
4678
4841
|
if (!raw.trimStart().startsWith("---")) {
|
|
4679
4842
|
stderr(`cleargate doctor --pricing: file has no frontmatter: ${absPath}`);
|
|
4680
|
-
|
|
4843
|
+
if (outcome) outcome.configError = true;
|
|
4844
|
+
exit(2);
|
|
4681
4845
|
return;
|
|
4682
4846
|
}
|
|
4683
4847
|
let fm;
|
|
@@ -4685,12 +4849,14 @@ async function runPricing(filePath, cwd, stdout, stderr, exit) {
|
|
|
4685
4849
|
fm = parseFrontmatter(raw).fm;
|
|
4686
4850
|
} catch {
|
|
4687
4851
|
stderr(`cleargate doctor --pricing: cannot parse frontmatter in: ${absPath}`);
|
|
4688
|
-
|
|
4852
|
+
if (outcome) outcome.configError = true;
|
|
4853
|
+
exit(2);
|
|
4689
4854
|
return;
|
|
4690
4855
|
}
|
|
4691
4856
|
const draftTokensRaw = fm["draft_tokens"];
|
|
4692
4857
|
if (!draftTokensRaw) {
|
|
4693
4858
|
stdout("draft_tokens unpopulated \u2014 run cleargate stamp-tokens first");
|
|
4859
|
+
if (outcome) outcome.blocker = true;
|
|
4694
4860
|
exit(1);
|
|
4695
4861
|
return;
|
|
4696
4862
|
}
|
|
@@ -4702,16 +4868,19 @@ async function runPricing(filePath, cwd, stdout, stderr, exit) {
|
|
|
4702
4868
|
draftTokens = JSON.parse(draftTokensRaw);
|
|
4703
4869
|
} catch {
|
|
4704
4870
|
stdout("draft_tokens unpopulated \u2014 run cleargate stamp-tokens first");
|
|
4871
|
+
if (outcome) outcome.blocker = true;
|
|
4705
4872
|
exit(1);
|
|
4706
4873
|
return;
|
|
4707
4874
|
}
|
|
4708
4875
|
} else {
|
|
4709
4876
|
stdout("draft_tokens unpopulated \u2014 run cleargate stamp-tokens first");
|
|
4877
|
+
if (outcome) outcome.blocker = true;
|
|
4710
4878
|
exit(1);
|
|
4711
4879
|
return;
|
|
4712
4880
|
}
|
|
4713
4881
|
if (draftTokens.input === null && draftTokens.output === null && draftTokens.cache_read === null && draftTokens.cache_creation === null) {
|
|
4714
4882
|
stdout("draft_tokens unpopulated \u2014 run cleargate stamp-tokens first");
|
|
4883
|
+
if (outcome) outcome.blocker = true;
|
|
4715
4884
|
exit(1);
|
|
4716
4885
|
return;
|
|
4717
4886
|
}
|
|
@@ -4729,18 +4898,95 @@ async function runPricing(filePath, cwd, stdout, stderr, exit) {
|
|
|
4729
4898
|
`${fileName}: ${model} \u2014 input:${input} output:${output} cache_read:${cacheRead} cache_creation:${cacheCreation} \u2248 $${usd.toFixed(4)}`
|
|
4730
4899
|
);
|
|
4731
4900
|
}
|
|
4901
|
+
function globMatch(pattern, filePath) {
|
|
4902
|
+
const normalPattern = pattern.replace(/\\/g, "/");
|
|
4903
|
+
const normalFile = filePath.replace(/\\/g, "/");
|
|
4904
|
+
const regexStr = normalPattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "\0").replace(/\*/g, "[^/]*").replace(//g, ".*");
|
|
4905
|
+
const re = new RegExp(`^${regexStr}$`);
|
|
4906
|
+
return re.test(normalFile);
|
|
4907
|
+
}
|
|
4908
|
+
async function runCanEdit(filePath, cwd, stdout, exit, outcome) {
|
|
4909
|
+
const activeSentinel = path24.join(cwd, ".cleargate", "sprint-runs", ".active");
|
|
4910
|
+
if (fs22.existsSync(activeSentinel)) {
|
|
4911
|
+
stdout("allowed: sprint active");
|
|
4912
|
+
return;
|
|
4913
|
+
}
|
|
4914
|
+
const pendingSyncDir = path24.join(cwd, ".cleargate", "delivery", "pending-sync");
|
|
4915
|
+
let files;
|
|
4916
|
+
try {
|
|
4917
|
+
files = fs22.readdirSync(pendingSyncDir).filter((f) => f.endsWith(".md")).map((f) => path24.join(pendingSyncDir, f));
|
|
4918
|
+
} catch {
|
|
4919
|
+
stdout("blocked: no_approved_stories");
|
|
4920
|
+
if (outcome) outcome.blocker = true;
|
|
4921
|
+
exit(1);
|
|
4922
|
+
return;
|
|
4923
|
+
}
|
|
4924
|
+
let hasApprovedStory = false;
|
|
4925
|
+
let coveredByStory = false;
|
|
4926
|
+
for (const storyPath of files) {
|
|
4927
|
+
let raw;
|
|
4928
|
+
try {
|
|
4929
|
+
raw = fs22.readFileSync(storyPath, "utf-8");
|
|
4930
|
+
} catch {
|
|
4931
|
+
continue;
|
|
4932
|
+
}
|
|
4933
|
+
if (!raw.trimStart().startsWith("---")) continue;
|
|
4934
|
+
let fm;
|
|
4935
|
+
try {
|
|
4936
|
+
fm = parseFrontmatter(raw).fm;
|
|
4937
|
+
} catch {
|
|
4938
|
+
continue;
|
|
4939
|
+
}
|
|
4940
|
+
if (fm["approved"] !== true) continue;
|
|
4941
|
+
hasApprovedStory = true;
|
|
4942
|
+
const implFilesRaw = fm["implementation_files"];
|
|
4943
|
+
if (implFilesRaw === void 0 || implFilesRaw === null) {
|
|
4944
|
+
coveredByStory = true;
|
|
4945
|
+
break;
|
|
4946
|
+
}
|
|
4947
|
+
if (Array.isArray(implFilesRaw)) {
|
|
4948
|
+
for (const pattern of implFilesRaw) {
|
|
4949
|
+
if (typeof pattern !== "string") continue;
|
|
4950
|
+
if (globMatch(pattern, filePath)) {
|
|
4951
|
+
coveredByStory = true;
|
|
4952
|
+
break;
|
|
4953
|
+
}
|
|
4954
|
+
}
|
|
4955
|
+
}
|
|
4956
|
+
if (coveredByStory) break;
|
|
4957
|
+
}
|
|
4958
|
+
if (!hasApprovedStory) {
|
|
4959
|
+
stdout("blocked: no_approved_stories");
|
|
4960
|
+
if (outcome) outcome.blocker = true;
|
|
4961
|
+
exit(1);
|
|
4962
|
+
return;
|
|
4963
|
+
}
|
|
4964
|
+
if (!coveredByStory) {
|
|
4965
|
+
stdout("blocked: file_not_in_implementation_files");
|
|
4966
|
+
if (outcome) outcome.blocker = true;
|
|
4967
|
+
exit(1);
|
|
4968
|
+
return;
|
|
4969
|
+
}
|
|
4970
|
+
stdout("allowed");
|
|
4971
|
+
}
|
|
4732
4972
|
async function doctorHandler(flags, cli) {
|
|
4733
4973
|
const cwd = cli?.cwd ?? process.cwd();
|
|
4734
4974
|
const now = cli?.now ? cli.now() : /* @__PURE__ */ new Date();
|
|
4735
4975
|
const stdout = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
|
|
4736
4976
|
const stderr = cli?.stderr ?? ((s) => process.stderr.write(s + "\n"));
|
|
4737
4977
|
const exit = cli?.exit ?? ((code) => process.exit(code));
|
|
4978
|
+
const outcome = { configError: false, blocker: false };
|
|
4979
|
+
let exitedEarly = false;
|
|
4980
|
+
const wrappedExit = (code) => {
|
|
4981
|
+
exitedEarly = true;
|
|
4982
|
+
return exit(code);
|
|
4983
|
+
};
|
|
4738
4984
|
let mode;
|
|
4739
4985
|
try {
|
|
4740
4986
|
mode = selectMode(flags);
|
|
4741
4987
|
} catch (err) {
|
|
4742
4988
|
stderr(err.message);
|
|
4743
|
-
exit(
|
|
4989
|
+
exit(2);
|
|
4744
4990
|
return;
|
|
4745
4991
|
}
|
|
4746
4992
|
switch (mode) {
|
|
@@ -4748,27 +4994,39 @@ async function doctorHandler(flags, cli) {
|
|
|
4748
4994
|
await runCheckScaffold(flags, cli ?? {}, cwd, now, stdout, stderr);
|
|
4749
4995
|
break;
|
|
4750
4996
|
case "hook-health":
|
|
4751
|
-
runHookHealth(stdout, cwd, now);
|
|
4997
|
+
runHookHealth(stdout, cwd, now, outcome);
|
|
4752
4998
|
break;
|
|
4753
4999
|
case "session-start":
|
|
4754
|
-
await runSessionStart(cwd, stdout);
|
|
5000
|
+
await runSessionStart(cwd, stdout, outcome);
|
|
4755
5001
|
break;
|
|
4756
5002
|
case "pricing":
|
|
4757
|
-
await runPricing(flags.pricingFile ?? "", cwd, stdout, stderr,
|
|
5003
|
+
await runPricing(flags.pricingFile ?? "", cwd, stdout, stderr, wrappedExit, outcome);
|
|
5004
|
+
break;
|
|
5005
|
+
case "can-edit":
|
|
5006
|
+
await runCanEdit(flags.canEditFile ?? "", cwd, stdout, wrappedExit, outcome);
|
|
4758
5007
|
break;
|
|
4759
5008
|
default: {
|
|
4760
5009
|
const exhaustiveCheck = mode;
|
|
4761
5010
|
stderr(`cleargate doctor: unknown mode '${String(exhaustiveCheck)}'`);
|
|
4762
|
-
exit(
|
|
5011
|
+
exit(2);
|
|
5012
|
+
return;
|
|
4763
5013
|
}
|
|
4764
5014
|
}
|
|
5015
|
+
if (exitedEarly) return;
|
|
5016
|
+
if (outcome.configError) {
|
|
5017
|
+
exit(2);
|
|
5018
|
+
} else if (outcome.blocker) {
|
|
5019
|
+
exit(1);
|
|
5020
|
+
} else {
|
|
5021
|
+
exit(0);
|
|
5022
|
+
}
|
|
4765
5023
|
}
|
|
4766
5024
|
|
|
4767
5025
|
// src/commands/gate.ts
|
|
4768
5026
|
init_cjs_shims();
|
|
4769
5027
|
var fs26 = __toESM(require("fs"), 1);
|
|
4770
5028
|
var path27 = __toESM(require("path"), 1);
|
|
4771
|
-
var
|
|
5029
|
+
var import_node_child_process7 = require("child_process");
|
|
4772
5030
|
|
|
4773
5031
|
// src/commands/execution-mode.ts
|
|
4774
5032
|
init_cjs_shims();
|
|
@@ -4877,6 +5135,14 @@ function parsePredicate(src) {
|
|
|
4877
5135
|
const value = parseValue(rawVal);
|
|
4878
5136
|
return { kind: "frontmatter", ref, field, op, value };
|
|
4879
5137
|
}
|
|
5138
|
+
const markerNotMatch = s.match(/^body does not contain marker ['"]([A-Z]+)['"]$/);
|
|
5139
|
+
if (markerNotMatch) {
|
|
5140
|
+
const marker = markerNotMatch[1];
|
|
5141
|
+
if (marker !== "TBD" && marker !== "TODO" && marker !== "FIXME") {
|
|
5142
|
+
throw new Error(`unsupported predicate shape: ${src}`);
|
|
5143
|
+
}
|
|
5144
|
+
return { kind: "marker-absence", marker };
|
|
5145
|
+
}
|
|
4880
5146
|
const bodyNotMatch = s.match(/^body does not contain ['"](.+)['"]$/);
|
|
4881
5147
|
if (bodyNotMatch) {
|
|
4882
5148
|
return { kind: "body-contains", needle: bodyNotMatch[1], negated: true };
|
|
@@ -4932,6 +5198,8 @@ function evaluate(predicate, doc, opts) {
|
|
|
4932
5198
|
return evalFrontmatter(parsed, doc, projectRoot);
|
|
4933
5199
|
case "body-contains":
|
|
4934
5200
|
return evalBodyContains(parsed, doc);
|
|
5201
|
+
case "marker-absence":
|
|
5202
|
+
return evalMarkerAbsence(parsed, doc);
|
|
4935
5203
|
case "section":
|
|
4936
5204
|
return evalSection(parsed, doc);
|
|
4937
5205
|
case "file-exists":
|
|
@@ -4954,6 +5222,26 @@ function evalFrontmatter(parsed, doc, projectRoot) {
|
|
|
4954
5222
|
detail: `frontmatter key '${parsed.ref}' is missing or null in ${doc.absPath}`
|
|
4955
5223
|
};
|
|
4956
5224
|
}
|
|
5225
|
+
const refStr = String(refVal);
|
|
5226
|
+
const looksLikeProse = refStr.length > 200 || /[ —–:()\n]/.test(refStr);
|
|
5227
|
+
if (looksLikeProse) {
|
|
5228
|
+
const waiver = doc.fm["proposal_gate_waiver"];
|
|
5229
|
+
const hasExplicitWaiver = waiver !== null && waiver !== void 0 && waiver !== false && String(waiver).trim() !== "" && String(waiver).trim() !== "false";
|
|
5230
|
+
const approvedBy = doc.fm["approved_by"];
|
|
5231
|
+
const approvedAt = doc.fm["approved_at"];
|
|
5232
|
+
const hasApprovalFields = approvedBy !== null && approvedBy !== void 0 && String(approvedBy).trim() !== "" && approvedAt !== null && approvedAt !== void 0 && String(approvedAt).trim() !== "";
|
|
5233
|
+
const hasWaiver = hasExplicitWaiver || hasApprovalFields;
|
|
5234
|
+
if (hasWaiver) {
|
|
5235
|
+
return {
|
|
5236
|
+
pass: true,
|
|
5237
|
+
detail: `context_source is prose; proposal-gate waiver per frontmatter approved_by/approved_at`
|
|
5238
|
+
};
|
|
5239
|
+
}
|
|
5240
|
+
return {
|
|
5241
|
+
pass: false,
|
|
5242
|
+
detail: `context_source is prose but no proposal_gate_waiver (approved_by + approved_at) found in frontmatter`
|
|
5243
|
+
};
|
|
5244
|
+
}
|
|
4957
5245
|
const linkedPath = resolveLinkedPath(String(refVal), doc.absPath, projectRoot);
|
|
4958
5246
|
if (!linkedPath) {
|
|
4959
5247
|
return {
|
|
@@ -5078,6 +5366,38 @@ function evalBodyContains(parsed, doc) {
|
|
|
5078
5366
|
return { pass: false, detail: `'${needle}' not found in body` };
|
|
5079
5367
|
}
|
|
5080
5368
|
}
|
|
5369
|
+
function evalMarkerAbsence(parsed, doc) {
|
|
5370
|
+
const { marker } = parsed;
|
|
5371
|
+
const lines = doc.body.split("\n");
|
|
5372
|
+
const templateSelfRefRe = /^\s*-\s*\[[x ]\]\s*0\s*"TBDs?"\s*exist/i;
|
|
5373
|
+
const markerRe = new RegExp(
|
|
5374
|
+
`(?:^|(?<=\\())${marker}(?=:)|\\(${marker}\\)|\\[${marker}\\]|(?<=//\\s*)${marker}(?!\\w)|(?<=#\\s*)${marker}(?!\\w)`,
|
|
5375
|
+
// # MARKER (comment)
|
|
5376
|
+
"g"
|
|
5377
|
+
);
|
|
5378
|
+
const bareLineRe = new RegExp(`^${marker}$`);
|
|
5379
|
+
const violations = [];
|
|
5380
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5381
|
+
const line = lines[i];
|
|
5382
|
+
if (templateSelfRefRe.test(line)) continue;
|
|
5383
|
+
const trimmed = line.trim();
|
|
5384
|
+
if (bareLineRe.test(trimmed)) {
|
|
5385
|
+
violations.push(i + 1);
|
|
5386
|
+
continue;
|
|
5387
|
+
}
|
|
5388
|
+
markerRe.lastIndex = 0;
|
|
5389
|
+
if (markerRe.test(line)) {
|
|
5390
|
+
violations.push(i + 1);
|
|
5391
|
+
}
|
|
5392
|
+
}
|
|
5393
|
+
if (violations.length > 0) {
|
|
5394
|
+
return {
|
|
5395
|
+
pass: false,
|
|
5396
|
+
detail: `${violations.length} marker occurrence${violations.length === 1 ? "" : "s"} of '${marker}' at line${violations.length === 1 ? "" : "s"} ${violations.join(", ")}`
|
|
5397
|
+
};
|
|
5398
|
+
}
|
|
5399
|
+
return { pass: true, detail: `no '${marker}' markers found in body` };
|
|
5400
|
+
}
|
|
5081
5401
|
function evalSection(parsed, doc) {
|
|
5082
5402
|
const body = doc.body;
|
|
5083
5403
|
const rawParts = body.split(/^(?=## )/m);
|
|
@@ -5446,7 +5766,7 @@ function gateQaHandler(opts, cli) {
|
|
|
5446
5766
|
const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
|
|
5447
5767
|
const stderrFn = cli?.stderr ?? ((s) => process.stderr.write(s + "\n"));
|
|
5448
5768
|
const exitFn = cli?.exit ?? ((code2) => process.exit(code2));
|
|
5449
|
-
const spawnFn = cli?.spawnFn ??
|
|
5769
|
+
const spawnFn = cli?.spawnFn ?? import_node_child_process7.spawnSync;
|
|
5450
5770
|
const sprintId = cli?.sprintId ?? "SPRINT-UNKNOWN";
|
|
5451
5771
|
const mode = readSprintExecutionMode(sprintId, {
|
|
5452
5772
|
sprintFilePath: cli?.sprintFilePath,
|
|
@@ -5472,7 +5792,7 @@ function gateArchHandler(opts, cli) {
|
|
|
5472
5792
|
const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
|
|
5473
5793
|
const stderrFn = cli?.stderr ?? ((s) => process.stderr.write(s + "\n"));
|
|
5474
5794
|
const exitFn = cli?.exit ?? ((code2) => process.exit(code2));
|
|
5475
|
-
const spawnFn = cli?.spawnFn ??
|
|
5795
|
+
const spawnFn = cli?.spawnFn ?? import_node_child_process7.spawnSync;
|
|
5476
5796
|
const sprintId = cli?.sprintId ?? "SPRINT-UNKNOWN";
|
|
5477
5797
|
const mode = readSprintExecutionMode(sprintId, {
|
|
5478
5798
|
sprintFilePath: cli?.sprintFilePath,
|
|
@@ -5497,13 +5817,13 @@ function gateArchHandler(opts, cli) {
|
|
|
5497
5817
|
|
|
5498
5818
|
// src/commands/gate-run.ts
|
|
5499
5819
|
init_cjs_shims();
|
|
5500
|
-
var
|
|
5820
|
+
var import_node_child_process8 = require("child_process");
|
|
5501
5821
|
var KNOWN_GATES = ["precommit", "test", "typecheck", "lint"];
|
|
5502
5822
|
function gateRunHandler(name, opts, cli) {
|
|
5503
5823
|
const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
|
|
5504
5824
|
const stderrFn = cli?.stderr ?? ((s) => process.stderr.write(s + "\n"));
|
|
5505
5825
|
const exitFn = cli?.exit ?? ((code) => process.exit(code));
|
|
5506
|
-
const spawnFn = cli?.spawnFn ??
|
|
5826
|
+
const spawnFn = cli?.spawnFn ?? import_node_child_process8.spawnSync;
|
|
5507
5827
|
const cwd = cli?.cwd ?? process.cwd();
|
|
5508
5828
|
const configLoaderFn = cli?.configLoader ?? loadWikiConfig;
|
|
5509
5829
|
if (!KNOWN_GATES.includes(name)) {
|
|
@@ -5536,7 +5856,7 @@ function gateRunHandler(name, opts, cli) {
|
|
|
5536
5856
|
init_cjs_shims();
|
|
5537
5857
|
var fs27 = __toESM(require("fs"), 1);
|
|
5538
5858
|
var path28 = __toESM(require("path"), 1);
|
|
5539
|
-
var
|
|
5859
|
+
var import_node_child_process9 = require("child_process");
|
|
5540
5860
|
var import_js_yaml7 = __toESM(require("js-yaml"), 1);
|
|
5541
5861
|
var TERMINAL_STATUSES2 = /* @__PURE__ */ new Set(["Completed", "Done", "Abandoned", "Closed", "Resolved"]);
|
|
5542
5862
|
function resolveRunScript(opts) {
|
|
@@ -5551,7 +5871,7 @@ function sprintInitHandler(opts, cli) {
|
|
|
5551
5871
|
const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
|
|
5552
5872
|
const stderrFn = cli?.stderr ?? ((s) => process.stderr.write(s + "\n"));
|
|
5553
5873
|
const exitFn = cli?.exit ?? defaultExit;
|
|
5554
|
-
const spawnFn = cli?.spawnFn ??
|
|
5874
|
+
const spawnFn = cli?.spawnFn ?? import_node_child_process9.spawnSync;
|
|
5555
5875
|
const mode = readSprintExecutionMode(opts.sprintId, {
|
|
5556
5876
|
sprintFilePath: cli?.sprintFilePath,
|
|
5557
5877
|
cwd: cli?.cwd
|
|
@@ -5573,7 +5893,7 @@ function sprintCloseHandler(opts, cli) {
|
|
|
5573
5893
|
const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
|
|
5574
5894
|
const stderrFn = cli?.stderr ?? ((s) => process.stderr.write(s + "\n"));
|
|
5575
5895
|
const exitFn = cli?.exit ?? defaultExit;
|
|
5576
|
-
const spawnFn = cli?.spawnFn ??
|
|
5896
|
+
const spawnFn = cli?.spawnFn ?? import_node_child_process9.spawnSync;
|
|
5577
5897
|
const mode = readSprintExecutionMode(opts.sprintId, {
|
|
5578
5898
|
sprintFilePath: cli?.sprintFilePath,
|
|
5579
5899
|
cwd: cli?.cwd
|
|
@@ -5678,7 +5998,7 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
5678
5998
|
const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
|
|
5679
5999
|
const stderrFn = cli?.stderr ?? ((s) => process.stderr.write(s + "\n"));
|
|
5680
6000
|
const exitFn = cli?.exit ?? defaultExit;
|
|
5681
|
-
const spawnFn = cli?.spawnFn ??
|
|
6001
|
+
const spawnFn = cli?.spawnFn ?? import_node_child_process9.spawnSync;
|
|
5682
6002
|
const cwd = cli?.cwd ?? process.cwd();
|
|
5683
6003
|
const wikiBuildFn = cli?.wikiBuildFn ?? (async (wCwd, wStdout) => {
|
|
5684
6004
|
const fakeExit = (code) => {
|
|
@@ -5903,7 +6223,7 @@ async function sprintArchiveHandler(opts, cli) {
|
|
|
5903
6223
|
init_cjs_shims();
|
|
5904
6224
|
var fs28 = __toESM(require("fs"), 1);
|
|
5905
6225
|
var path29 = __toESM(require("path"), 1);
|
|
5906
|
-
var
|
|
6226
|
+
var import_node_child_process10 = require("child_process");
|
|
5907
6227
|
function defaultExit2(code) {
|
|
5908
6228
|
return process.exit(code);
|
|
5909
6229
|
}
|
|
@@ -5929,7 +6249,7 @@ function storyStartHandler(opts, cli) {
|
|
|
5929
6249
|
const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
|
|
5930
6250
|
const stderrFn = cli?.stderr ?? ((s) => process.stderr.write(s + "\n"));
|
|
5931
6251
|
const exitFn = cli?.exit ?? defaultExit2;
|
|
5932
|
-
const spawnFn = cli?.spawnFn ??
|
|
6252
|
+
const spawnFn = cli?.spawnFn ?? import_node_child_process10.spawnSync;
|
|
5933
6253
|
const cwd = cli?.cwd ?? process.cwd();
|
|
5934
6254
|
const sprintId = cli?.sprintId ?? resolveSprintIdFromSentinel(cwd) ?? "SPRINT-UNKNOWN";
|
|
5935
6255
|
const mode = readSprintExecutionMode(sprintId, {
|
|
@@ -6007,7 +6327,7 @@ function storyCompleteHandler(opts, cli) {
|
|
|
6007
6327
|
const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
|
|
6008
6328
|
const stderrFn = cli?.stderr ?? ((s) => process.stderr.write(s + "\n"));
|
|
6009
6329
|
const exitFn = cli?.exit ?? defaultExit2;
|
|
6010
|
-
const spawnFn = cli?.spawnFn ??
|
|
6330
|
+
const spawnFn = cli?.spawnFn ?? import_node_child_process10.spawnSync;
|
|
6011
6331
|
const cwd = cli?.cwd ?? process.cwd();
|
|
6012
6332
|
const sprintId = cli?.sprintId ?? resolveSprintIdFromSentinel(cwd) ?? "SPRINT-UNKNOWN";
|
|
6013
6333
|
const mode = readSprintExecutionMode(sprintId, {
|
|
@@ -6118,7 +6438,7 @@ function storyCompleteHandler(opts, cli) {
|
|
|
6118
6438
|
// src/commands/state.ts
|
|
6119
6439
|
init_cjs_shims();
|
|
6120
6440
|
var path30 = __toESM(require("path"), 1);
|
|
6121
|
-
var
|
|
6441
|
+
var import_node_child_process11 = require("child_process");
|
|
6122
6442
|
function defaultExit3(code) {
|
|
6123
6443
|
return process.exit(code);
|
|
6124
6444
|
}
|
|
@@ -6131,7 +6451,7 @@ function stateUpdateHandler(opts, cli) {
|
|
|
6131
6451
|
const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
|
|
6132
6452
|
const stderrFn = cli?.stderr ?? ((s) => process.stderr.write(s + "\n"));
|
|
6133
6453
|
const exitFn = cli?.exit ?? defaultExit3;
|
|
6134
|
-
const spawnFn = cli?.spawnFn ??
|
|
6454
|
+
const spawnFn = cli?.spawnFn ?? import_node_child_process11.spawnSync;
|
|
6135
6455
|
const cwd = cli?.cwd;
|
|
6136
6456
|
const sprintId = cli?.sprintId ?? resolveSprintIdFromSentinel(cwd) ?? "SPRINT-UNKNOWN";
|
|
6137
6457
|
const mode = readSprintExecutionMode(sprintId, {
|
|
@@ -6158,7 +6478,7 @@ function stateValidateHandler(opts, cli) {
|
|
|
6158
6478
|
const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
|
|
6159
6479
|
const stderrFn = cli?.stderr ?? ((s) => process.stderr.write(s + "\n"));
|
|
6160
6480
|
const exitFn = cli?.exit ?? defaultExit3;
|
|
6161
|
-
const spawnFn = cli?.spawnFn ??
|
|
6481
|
+
const spawnFn = cli?.spawnFn ?? import_node_child_process11.spawnSync;
|
|
6162
6482
|
const mode = readSprintExecutionMode(opts.sprintId, {
|
|
6163
6483
|
sprintFilePath: cli?.sprintFilePath,
|
|
6164
6484
|
cwd: cli?.cwd
|
|
@@ -6635,7 +6955,7 @@ async function promptMergeChoice(opts) {
|
|
|
6635
6955
|
|
|
6636
6956
|
// src/lib/editor.ts
|
|
6637
6957
|
init_cjs_shims();
|
|
6638
|
-
var
|
|
6958
|
+
var import_node_child_process12 = require("child_process");
|
|
6639
6959
|
async function openInEditor(filePath, opts) {
|
|
6640
6960
|
const env = opts?.env ?? process.env;
|
|
6641
6961
|
const editor = opts?.editor ?? env["EDITOR"] ?? env["VISUAL"];
|
|
@@ -6643,7 +6963,7 @@ async function openInEditor(filePath, opts) {
|
|
|
6643
6963
|
throw new Error("$EDITOR not set; cannot [e]dit option. Set the EDITOR environment variable.");
|
|
6644
6964
|
}
|
|
6645
6965
|
return new Promise((resolve14, reject) => {
|
|
6646
|
-
const child = (0,
|
|
6966
|
+
const child = (0, import_node_child_process12.spawn)(editor, [filePath], {
|
|
6647
6967
|
stdio: "inherit",
|
|
6648
6968
|
env: { ...env }
|
|
6649
6969
|
});
|
|
@@ -6918,7 +7238,7 @@ init_cjs_shims();
|
|
|
6918
7238
|
var fs31 = __toESM(require("fs"), 1);
|
|
6919
7239
|
var fsp2 = __toESM(require("fs/promises"), 1);
|
|
6920
7240
|
var path34 = __toESM(require("path"), 1);
|
|
6921
|
-
var
|
|
7241
|
+
var import_node_child_process13 = require("child_process");
|
|
6922
7242
|
var USER_ARTIFACT_TIERS = ["user-artifact"];
|
|
6923
7243
|
var FRAMEWORK_TIERS = ["protocol", "template", "agent", "hook", "skill", "cli-config", "derived"];
|
|
6924
7244
|
function parseTierList(raw) {
|
|
@@ -6959,7 +7279,7 @@ function resolveProjectName(target) {
|
|
|
6959
7279
|
function detectUncommittedChanges(target, manifestPaths, gitRunner) {
|
|
6960
7280
|
const run = gitRunner ?? ((args) => {
|
|
6961
7281
|
try {
|
|
6962
|
-
const out = (0,
|
|
7282
|
+
const out = (0, import_node_child_process13.execSync)(["git", ...args].join(" "), {
|
|
6963
7283
|
cwd: target,
|
|
6964
7284
|
stdio: ["pipe", "pipe", "pipe"],
|
|
6965
7285
|
encoding: "utf-8"
|
|
@@ -9165,6 +9485,109 @@ async function adminLoginHandler(opts = {}) {
|
|
|
9165
9485
|
stdout(`Credentials saved to ${authFilePath} (chmod 600).`);
|
|
9166
9486
|
}
|
|
9167
9487
|
|
|
9488
|
+
// src/commands/hotfix.ts
|
|
9489
|
+
init_cjs_shims();
|
|
9490
|
+
var fs36 = __toESM(require("fs"), 1);
|
|
9491
|
+
var path47 = __toESM(require("path"), 1);
|
|
9492
|
+
function defaultExit4(code) {
|
|
9493
|
+
return process.exit(code);
|
|
9494
|
+
}
|
|
9495
|
+
var SLUG_RE = /^[a-z0-9-]+$/;
|
|
9496
|
+
var HOTFIX_FILE_RE = /^HOTFIX-(\d+)_.*\.md$/;
|
|
9497
|
+
function maxHotfixId(pendingDir) {
|
|
9498
|
+
let max = 0;
|
|
9499
|
+
let entries;
|
|
9500
|
+
try {
|
|
9501
|
+
entries = fs36.readdirSync(pendingDir);
|
|
9502
|
+
} catch {
|
|
9503
|
+
return 0;
|
|
9504
|
+
}
|
|
9505
|
+
for (const entry of entries) {
|
|
9506
|
+
const m = HOTFIX_FILE_RE.exec(entry);
|
|
9507
|
+
if (m) {
|
|
9508
|
+
const n = parseInt(m[1], 10);
|
|
9509
|
+
if (n > max) max = n;
|
|
9510
|
+
}
|
|
9511
|
+
}
|
|
9512
|
+
return max;
|
|
9513
|
+
}
|
|
9514
|
+
function countActiveHotfixes(repoRoot) {
|
|
9515
|
+
const pendingDir = path47.join(repoRoot, ".cleargate", "delivery", "pending-sync");
|
|
9516
|
+
const archiveDir = path47.join(repoRoot, ".cleargate", "delivery", "archive");
|
|
9517
|
+
const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1e3;
|
|
9518
|
+
let count = 0;
|
|
9519
|
+
let pendingEntries = [];
|
|
9520
|
+
try {
|
|
9521
|
+
pendingEntries = fs36.readdirSync(pendingDir);
|
|
9522
|
+
} catch {
|
|
9523
|
+
}
|
|
9524
|
+
for (const entry of pendingEntries) {
|
|
9525
|
+
if (entry.startsWith("HOTFIX-") && entry.endsWith(".md")) count++;
|
|
9526
|
+
}
|
|
9527
|
+
let archiveEntries = [];
|
|
9528
|
+
try {
|
|
9529
|
+
archiveEntries = fs36.readdirSync(archiveDir);
|
|
9530
|
+
} catch {
|
|
9531
|
+
}
|
|
9532
|
+
for (const entry of archiveEntries) {
|
|
9533
|
+
if (entry.startsWith("HOTFIX-") && entry.endsWith(".md")) {
|
|
9534
|
+
try {
|
|
9535
|
+
const stat = fs36.statSync(path47.join(archiveDir, entry));
|
|
9536
|
+
if (stat.mtimeMs >= sevenDaysAgo) count++;
|
|
9537
|
+
} catch {
|
|
9538
|
+
}
|
|
9539
|
+
}
|
|
9540
|
+
}
|
|
9541
|
+
return count;
|
|
9542
|
+
}
|
|
9543
|
+
function resolveTemplatePath(repoRoot) {
|
|
9544
|
+
return path47.join(repoRoot, ".cleargate", "templates", "hotfix.md");
|
|
9545
|
+
}
|
|
9546
|
+
function hotfixNewHandler(opts, cli) {
|
|
9547
|
+
const stdoutFn = cli?.stdout ?? ((s) => process.stdout.write(s + "\n"));
|
|
9548
|
+
const stderrFn = cli?.stderr ?? ((s) => process.stderr.write(s + "\n"));
|
|
9549
|
+
const exitFn = cli?.exit ?? defaultExit4;
|
|
9550
|
+
const repoRoot = cli?.cwd ?? process.cwd();
|
|
9551
|
+
const now = cli?.now ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
9552
|
+
if (!SLUG_RE.test(opts.slug)) {
|
|
9553
|
+
stderrFn(`[cleargate hotfix new] slug must match ^[a-z0-9-]+$ (got: "${opts.slug}")`);
|
|
9554
|
+
return exitFn(1);
|
|
9555
|
+
}
|
|
9556
|
+
const activeCount = countActiveHotfixes(repoRoot);
|
|
9557
|
+
if (activeCount >= 3) {
|
|
9558
|
+
stderrFn(
|
|
9559
|
+
`Hotfix cap: \u22643 per rolling 7-day window. Currently ${activeCount} active. Bundle into a sprint or downgrade one to a CR.`
|
|
9560
|
+
);
|
|
9561
|
+
return exitFn(1);
|
|
9562
|
+
}
|
|
9563
|
+
const pendingDir = path47.join(repoRoot, ".cleargate", "delivery", "pending-sync");
|
|
9564
|
+
const maxId = maxHotfixId(pendingDir);
|
|
9565
|
+
const nextId = maxId + 1;
|
|
9566
|
+
const idStr = `HOTFIX-${String(nextId).padStart(3, "0")}`;
|
|
9567
|
+
const templatePath = resolveTemplatePath(repoRoot);
|
|
9568
|
+
let templateContent;
|
|
9569
|
+
try {
|
|
9570
|
+
templateContent = fs36.readFileSync(templatePath, "utf8");
|
|
9571
|
+
} catch {
|
|
9572
|
+
stderrFn(`[cleargate hotfix new] template not found: ${templatePath}`);
|
|
9573
|
+
return exitFn(2);
|
|
9574
|
+
}
|
|
9575
|
+
const content = templateContent.replace(/\{ID\}/g, idStr).replace(/\{SLUG\}/g, opts.slug).replace(/\{ISO\}/g, now);
|
|
9576
|
+
const fileSlug = opts.slug.replace(/-/g, "_");
|
|
9577
|
+
const fileName = `${idStr}_${fileSlug}.md`;
|
|
9578
|
+
const outPath = path47.join(pendingDir, fileName);
|
|
9579
|
+
try {
|
|
9580
|
+
fs36.mkdirSync(pendingDir, { recursive: true });
|
|
9581
|
+
fs36.writeFileSync(outPath, content, "utf8");
|
|
9582
|
+
} catch (err) {
|
|
9583
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
9584
|
+
stderrFn(`[cleargate hotfix new] write failed: ${msg}`);
|
|
9585
|
+
return exitFn(1);
|
|
9586
|
+
}
|
|
9587
|
+
stdoutFn(`[cleargate hotfix new] created: ${outPath}`);
|
|
9588
|
+
return exitFn(0);
|
|
9589
|
+
}
|
|
9590
|
+
|
|
9168
9591
|
// src/cli.ts
|
|
9169
9592
|
var program = new import_commander.Command();
|
|
9170
9593
|
program.name("cleargate").description("ClearGate CLI \u2014 connects AI agent teams to the ClearGate MCP server").version(package_default.version, "-V, --version").option("--profile <name>", "configuration profile to use", "default").option("--mcp-url <url>", "MCP server URL (overrides config file and env)").showHelpAfterError("(use `cleargate --help`)");
|
|
@@ -9181,8 +9604,8 @@ program.command("join <invite-url>").description("join a ClearGate workspace usi
|
|
|
9181
9604
|
...cmdOpts.code !== void 0 ? { code: cmdOpts.code } : {}
|
|
9182
9605
|
});
|
|
9183
9606
|
});
|
|
9184
|
-
program.command("init").description("initialise a repo with ClearGate scaffold (CLAUDE.md block, hook config, agents, templates)").option("--force", "overwrite existing files that differ from the bundled payload").option("--yes", "non-interactive: accept all defaults without prompting").action(async (opts) => {
|
|
9185
|
-
await initHandler({ force: opts.force ?? false, yes: opts.yes ?? false });
|
|
9607
|
+
program.command("init").description("initialise a repo with ClearGate scaffold (CLAUDE.md block, hook config, agents, templates)").option("--force", "overwrite existing files that differ from the bundled payload").option("--yes", "non-interactive: accept all defaults without prompting").option("--pin <ver>", "CR-009: pin hook resolver to a specific cleargate CLI version (default: package version)").action(async (opts) => {
|
|
9608
|
+
await initHandler({ force: opts.force ?? false, yes: opts.yes ?? false, pin: opts.pin });
|
|
9186
9609
|
});
|
|
9187
9610
|
program.command("whoami").description("print the currently authenticated agent identity").action(async () => {
|
|
9188
9611
|
const { whoamiHandler: whoamiHandler2 } = await Promise.resolve().then(() => (init_whoami(), whoami_exports));
|
|
@@ -9304,14 +9727,20 @@ admin.command("bootstrap-root <handle>").description("seed the first root admin
|
|
|
9304
9727
|
const { bootstrapRootHandler: bootstrapRootHandler2 } = await Promise.resolve().then(() => (init_bootstrap_root(), bootstrap_root_exports));
|
|
9305
9728
|
await bootstrapRootHandler2({ handle, databaseUrl: opts.databaseUrl, force: opts.force ?? false });
|
|
9306
9729
|
});
|
|
9307
|
-
program.command("doctor").description("diagnose scaffold drift, hook health, blocked items, and token cost").option("--check-scaffold", "check scaffold files for drift against install snapshot").option("--session-start-mode", "hidden: enables daily throttle (used by session-start hook)", false).option("--session-start", "emit blocked pending-sync items summary (used by SessionStart hook)").option("--pricing <file>", "compute USD cost estimate from a work item's draft_tokens").option("-v, --verbose", "show per-file drift detail").addHelpText("after", [
|
|
9730
|
+
program.command("doctor").description("diagnose scaffold drift, hook health, blocked items, and token cost").option("--check-scaffold", "check scaffold files for drift against install snapshot").option("--session-start-mode", "hidden: enables daily throttle (used by session-start hook)", false).option("--session-start", "emit blocked pending-sync items summary (used by SessionStart hook)").option("--pricing <file>", "compute USD cost estimate from a work item's draft_tokens").option("--can-edit <file>", "CR-008: exit 0 if editing file is allowed, exit 1 if planning required").option("--cwd <dir>", "working directory for the doctor check (default: process.cwd())").option("-v, --verbose", "show per-file drift detail").addHelpText("after", [
|
|
9308
9731
|
"",
|
|
9309
9732
|
"Modes (mutually exclusive):",
|
|
9310
9733
|
" --check-scaffold Compute drift for all tracked scaffold files.",
|
|
9311
9734
|
" Writes .cleargate/.drift-state.json.",
|
|
9312
9735
|
" --session-start List blocked pending-sync items (\u226410, \u2264100 tokens).",
|
|
9313
9736
|
" --pricing <file> Compute USD estimate from a work item's draft_tokens.",
|
|
9314
|
-
"
|
|
9737
|
+
" --can-edit <file> Check if editing a file requires a planning work item.",
|
|
9738
|
+
" (default) Print a minimal hook-config health report.",
|
|
9739
|
+
"",
|
|
9740
|
+
"Exit codes:",
|
|
9741
|
+
" 0 Clean \u2014 no blockers, no config errors.",
|
|
9742
|
+
" 1 Blocked items or advisory issues \u2014 see stdout.",
|
|
9743
|
+
" 2 ClearGate misconfigured or partially installed \u2014 see stdout for remediation."
|
|
9315
9744
|
].join("\n")).action(async (opts) => {
|
|
9316
9745
|
await doctorHandler({
|
|
9317
9746
|
checkScaffold: opts.checkScaffold,
|
|
@@ -9319,8 +9748,10 @@ program.command("doctor").description("diagnose scaffold drift, hook health, blo
|
|
|
9319
9748
|
sessionStart: opts.sessionStart,
|
|
9320
9749
|
pricing: !!opts.pricing,
|
|
9321
9750
|
pricingFile: opts.pricing,
|
|
9751
|
+
canEdit: !!opts.canEdit,
|
|
9752
|
+
canEditFile: opts.canEdit,
|
|
9322
9753
|
verbose: opts.verbose
|
|
9323
|
-
});
|
|
9754
|
+
}, opts.cwd ? { cwd: opts.cwd } : void 0);
|
|
9324
9755
|
});
|
|
9325
9756
|
program.command("upgrade").description("three-way merge scaffold files with upstream changes").option("--dry-run", "print plan without making any changes").option("--yes", 'auto-accept "take theirs" for all merge-3way files (non-interactive)').option("--only <tier>", "restrict to a specific scaffold tier (protocol/template/agent/hook/skill/cli-config)").addHelpText("after", [
|
|
9326
9757
|
"",
|
|
@@ -9342,7 +9773,7 @@ program.command("uninstall").description("remove ClearGate scaffold from a proje
|
|
|
9342
9773
|
"",
|
|
9343
9774
|
"Always removed (no prompt): .claude/agents/*.md, ClearGate hooks,",
|
|
9344
9775
|
" .claude/skills/flashcard/, CLAUDE.md CLEARGATE block,",
|
|
9345
|
-
"
|
|
9776
|
+
" `cleargate` from package.json, .install-manifest.json, .drift-state.json.",
|
|
9346
9777
|
"",
|
|
9347
9778
|
"Non-git targets: uncommitted-changes check is skipped silently."
|
|
9348
9779
|
].join("\n")).action(async (opts) => {
|
|
@@ -9395,5 +9826,9 @@ program.command("sync-log").description("filter and print sync-log entries").opt
|
|
|
9395
9826
|
limit: opts.limit !== void 0 ? parseInt(opts.limit, 10) : 50
|
|
9396
9827
|
});
|
|
9397
9828
|
});
|
|
9829
|
+
var hotfix = program.command("hotfix").description("hotfix lane commands (off-sprint trivial fix scaffolding)");
|
|
9830
|
+
hotfix.command("new <slug>").description("scaffold a new HOTFIX-NNN_<slug>.md in pending-sync/").action((slug) => {
|
|
9831
|
+
hotfixNewHandler({ slug });
|
|
9832
|
+
});
|
|
9398
9833
|
void program.parseAsync(process.argv);
|
|
9399
9834
|
//# sourceMappingURL=cli.cjs.map
|