cleargate 0.2.0 → 0.2.1
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 +5 -5
- package/dist/cli.cjs +119 -132
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +119 -132
- package/dist/cli.js.map +1 -1
- package/dist/templates/cleargate-planning/.claude/agents/reporter.md +15 -0
- package/dist/templates/cleargate-planning/.claude/skills/flashcard/SKILL.md +31 -12
- package/dist/templates/cleargate-planning/.cleargate/FLASHCARD.md +2 -1
- package/dist/templates/cleargate-planning/.cleargate/templates/story.md +10 -0
- package/dist/templates/cleargate-planning/CLAUDE.md +1 -0
- package/dist/templates/cleargate-planning/MANIFEST.json +5 -5
- package/package.json +1 -1
- package/templates/cleargate-planning/.claude/agents/reporter.md +15 -0
- package/templates/cleargate-planning/.claude/skills/flashcard/SKILL.md +31 -12
- package/templates/cleargate-planning/.cleargate/FLASHCARD.md +2 -1
- package/templates/cleargate-planning/.cleargate/templates/story.md +10 -0
- package/templates/cleargate-planning/CLAUDE.md +1 -0
- package/templates/cleargate-planning/MANIFEST.json +5 -5
package/dist/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ import { Command } from "commander";
|
|
|
9
9
|
// package.json
|
|
10
10
|
var package_default = {
|
|
11
11
|
name: "cleargate",
|
|
12
|
-
version: "0.2.
|
|
12
|
+
version: "0.2.1",
|
|
13
13
|
private: false,
|
|
14
14
|
type: "module",
|
|
15
15
|
description: "Planning framework for Claude Code agents \u2014 sprint/epic/story protocol, four-agent loop (architect/developer/qa/reporter), Karpathy-style awareness wiki.",
|
|
@@ -358,6 +358,7 @@ function getCodebaseVersion(opts) {
|
|
|
358
358
|
import * as fs3 from "fs/promises";
|
|
359
359
|
|
|
360
360
|
// src/wiki/parse-frontmatter.ts
|
|
361
|
+
import yaml from "js-yaml";
|
|
361
362
|
function parseFrontmatter(raw) {
|
|
362
363
|
const lines = raw.split("\n");
|
|
363
364
|
if (lines[0] !== "---") {
|
|
@@ -373,71 +374,45 @@ function parseFrontmatter(raw) {
|
|
|
373
374
|
if (closeIdx === -1) {
|
|
374
375
|
throw new Error("parseFrontmatter: missing closing ---");
|
|
375
376
|
}
|
|
376
|
-
const
|
|
377
|
+
const yamlText = lines.slice(1, closeIdx).join("\n");
|
|
377
378
|
const bodyLines = lines.slice(closeIdx + 1);
|
|
378
379
|
if (bodyLines[0] === "") bodyLines.shift();
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
continue;
|
|
395
|
-
}
|
|
396
|
-
fm[key] = inner.split(",").map((s) => s.trim().replace(/^["']|["']$/g, ""));
|
|
397
|
-
continue;
|
|
398
|
-
}
|
|
399
|
-
if (val.startsWith("{")) {
|
|
400
|
-
fm[key] = val;
|
|
401
|
-
continue;
|
|
402
|
-
}
|
|
403
|
-
fm[key] = val.replace(/^["']|["']$/g, "");
|
|
380
|
+
const body = bodyLines.join("\n");
|
|
381
|
+
if (yamlText.trim() === "") {
|
|
382
|
+
return { fm: {}, body };
|
|
383
|
+
}
|
|
384
|
+
let parsed;
|
|
385
|
+
try {
|
|
386
|
+
parsed = yaml.load(yamlText, { schema: yaml.CORE_SCHEMA });
|
|
387
|
+
} catch (err) {
|
|
388
|
+
throw new Error(`parseFrontmatter: invalid YAML: ${err.message}`);
|
|
389
|
+
}
|
|
390
|
+
if (parsed === null || parsed === void 0) {
|
|
391
|
+
return { fm: {}, body };
|
|
392
|
+
}
|
|
393
|
+
if (typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
394
|
+
throw new Error("parseFrontmatter: frontmatter is not a YAML mapping");
|
|
404
395
|
}
|
|
405
|
-
return { fm, body
|
|
396
|
+
return { fm: parsed, body };
|
|
406
397
|
}
|
|
407
398
|
|
|
408
399
|
// src/lib/frontmatter-yaml.ts
|
|
400
|
+
import yaml2 from "js-yaml";
|
|
409
401
|
function serializeFrontmatter(fm) {
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
}
|
|
425
|
-
} else {
|
|
426
|
-
const s = String(val);
|
|
427
|
-
if (s.startsWith("{")) {
|
|
428
|
-
lines.push(`${key}: ${s}`);
|
|
429
|
-
} else {
|
|
430
|
-
const needsQuotes = /[:#\[\]{}&*!|>'"%@`\n]/.test(s) || s.trim() !== s || s === "" || s === "null" || s === "true" || s === "false";
|
|
431
|
-
if (needsQuotes) {
|
|
432
|
-
lines.push(`${key}: "${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`);
|
|
433
|
-
} else {
|
|
434
|
-
lines.push(`${key}: ${s}`);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
lines.push("---");
|
|
440
|
-
return lines.join("\n");
|
|
402
|
+
if (Object.keys(fm).length === 0) {
|
|
403
|
+
return "---\n---";
|
|
404
|
+
}
|
|
405
|
+
const yamlBody = yaml2.dump(fm, {
|
|
406
|
+
schema: yaml2.CORE_SCHEMA,
|
|
407
|
+
lineWidth: -1,
|
|
408
|
+
noRefs: true,
|
|
409
|
+
noCompatMode: true,
|
|
410
|
+
quotingType: '"',
|
|
411
|
+
forceQuotes: false
|
|
412
|
+
});
|
|
413
|
+
return `---
|
|
414
|
+
${yamlBody.replace(/\n+$/, "")}
|
|
415
|
+
---`;
|
|
441
416
|
}
|
|
442
417
|
function toIsoSecond(d) {
|
|
443
418
|
return d.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
@@ -2034,7 +2009,7 @@ function loadWikiPages(wikiRoot) {
|
|
|
2034
2009
|
import * as fs15 from "fs";
|
|
2035
2010
|
import * as path15 from "path";
|
|
2036
2011
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
2037
|
-
import
|
|
2012
|
+
import yaml3 from "js-yaml";
|
|
2038
2013
|
|
|
2039
2014
|
// src/lib/work-item-type.ts
|
|
2040
2015
|
var FM_KEY_MAP = [
|
|
@@ -2281,7 +2256,7 @@ function parseCachedGateResult(raw) {
|
|
|
2281
2256
|
if (typeof raw === "string") {
|
|
2282
2257
|
if (!raw.startsWith("{")) return null;
|
|
2283
2258
|
try {
|
|
2284
|
-
const parsed =
|
|
2259
|
+
const parsed = yaml3.load(raw);
|
|
2285
2260
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
|
|
2286
2261
|
const p = parsed;
|
|
2287
2262
|
return { pass: p["pass"], failing_criteria: p["failing_criteria"], last_gate_check: p["last_gate_check"] };
|
|
@@ -2764,15 +2739,23 @@ function emitSummary(driftMap, verbose, stdout) {
|
|
|
2764
2739
|
var SESSION_START_MAX_ITEMS = 10;
|
|
2765
2740
|
var SESSION_START_MAX_CHARS = 400;
|
|
2766
2741
|
function parseCachedGateResult2(raw) {
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2742
|
+
if (raw == null) return null;
|
|
2743
|
+
let parsed = null;
|
|
2744
|
+
if (typeof raw === "object" && !Array.isArray(raw)) {
|
|
2745
|
+
parsed = raw;
|
|
2746
|
+
} else if (typeof raw === "string") {
|
|
2747
|
+
try {
|
|
2748
|
+
parsed = JSON.parse(raw);
|
|
2749
|
+
} catch {
|
|
2750
|
+
return null;
|
|
2751
|
+
}
|
|
2752
|
+
} else {
|
|
2774
2753
|
return null;
|
|
2775
2754
|
}
|
|
2755
|
+
return {
|
|
2756
|
+
pass: parsed.pass ?? null,
|
|
2757
|
+
failing_criteria: parsed.failing_criteria ?? []
|
|
2758
|
+
};
|
|
2776
2759
|
}
|
|
2777
2760
|
async function runSessionStart(cwd, stdout) {
|
|
2778
2761
|
const pendingSyncDir = path18.join(cwd, ".cleargate", "delivery", "pending-sync");
|
|
@@ -2797,9 +2780,7 @@ async function runSessionStart(cwd, stdout) {
|
|
|
2797
2780
|
} catch {
|
|
2798
2781
|
continue;
|
|
2799
2782
|
}
|
|
2800
|
-
const
|
|
2801
|
-
if (typeof gateRaw !== "string") continue;
|
|
2802
|
-
const gate2 = parseCachedGateResult2(gateRaw);
|
|
2783
|
+
const gate2 = parseCachedGateResult2(fm["cached_gate_result"]);
|
|
2803
2784
|
if (!gate2 || gate2.pass !== false) continue;
|
|
2804
2785
|
const idKeys = ["story_id", "epic_id", "proposal_id", "cr_id", "bug_id", "sprint_id"];
|
|
2805
2786
|
let itemId = "";
|
|
@@ -2864,15 +2845,23 @@ async function runPricing(filePath, cwd, stdout, stderr, exit) {
|
|
|
2864
2845
|
return;
|
|
2865
2846
|
}
|
|
2866
2847
|
const draftTokensRaw = fm["draft_tokens"];
|
|
2867
|
-
if (!draftTokensRaw
|
|
2848
|
+
if (!draftTokensRaw) {
|
|
2868
2849
|
stdout("draft_tokens unpopulated \u2014 run cleargate stamp-tokens first");
|
|
2869
2850
|
exit(1);
|
|
2870
2851
|
return;
|
|
2871
2852
|
}
|
|
2872
2853
|
let draftTokens;
|
|
2873
|
-
|
|
2874
|
-
draftTokens =
|
|
2875
|
-
}
|
|
2854
|
+
if (typeof draftTokensRaw === "object" && !Array.isArray(draftTokensRaw)) {
|
|
2855
|
+
draftTokens = draftTokensRaw;
|
|
2856
|
+
} else if (typeof draftTokensRaw === "string") {
|
|
2857
|
+
try {
|
|
2858
|
+
draftTokens = JSON.parse(draftTokensRaw);
|
|
2859
|
+
} catch {
|
|
2860
|
+
stdout("draft_tokens unpopulated \u2014 run cleargate stamp-tokens first");
|
|
2861
|
+
exit(1);
|
|
2862
|
+
return;
|
|
2863
|
+
}
|
|
2864
|
+
} else {
|
|
2876
2865
|
stdout("draft_tokens unpopulated \u2014 run cleargate stamp-tokens first");
|
|
2877
2866
|
exit(1);
|
|
2878
2867
|
return;
|
|
@@ -2934,7 +2923,7 @@ async function doctorHandler(flags, cli) {
|
|
|
2934
2923
|
// src/commands/gate.ts
|
|
2935
2924
|
import * as fs20 from "fs";
|
|
2936
2925
|
import * as path20 from "path";
|
|
2937
|
-
import
|
|
2926
|
+
import yaml5 from "js-yaml";
|
|
2938
2927
|
|
|
2939
2928
|
// src/lib/readiness-predicates.ts
|
|
2940
2929
|
import * as fs18 from "fs";
|
|
@@ -3262,7 +3251,7 @@ function evalStatusOf(parsed, opts, projectRoot) {
|
|
|
3262
3251
|
|
|
3263
3252
|
// src/lib/frontmatter-cache.ts
|
|
3264
3253
|
import * as fs19 from "fs/promises";
|
|
3265
|
-
import
|
|
3254
|
+
import yaml4 from "js-yaml";
|
|
3266
3255
|
async function readCachedGate(absPath) {
|
|
3267
3256
|
let raw;
|
|
3268
3257
|
try {
|
|
@@ -3276,20 +3265,7 @@ async function readCachedGate(absPath) {
|
|
|
3276
3265
|
} catch {
|
|
3277
3266
|
return null;
|
|
3278
3267
|
}
|
|
3279
|
-
|
|
3280
|
-
if (cached === void 0 || cached === null) return null;
|
|
3281
|
-
if (typeof cached === "string") {
|
|
3282
|
-
return parseCachedGateString(cached);
|
|
3283
|
-
}
|
|
3284
|
-
if (typeof cached === "object" && !Array.isArray(cached)) {
|
|
3285
|
-
const c = cached;
|
|
3286
|
-
return {
|
|
3287
|
-
pass: Boolean(c["pass"]),
|
|
3288
|
-
failing_criteria: Array.isArray(c["failing_criteria"]) ? c["failing_criteria"] : [],
|
|
3289
|
-
last_gate_check: String(c["last_gate_check"] ?? "")
|
|
3290
|
-
};
|
|
3291
|
-
}
|
|
3292
|
-
return null;
|
|
3268
|
+
return coerceCachedGate(fm["cached_gate_result"]);
|
|
3293
3269
|
}
|
|
3294
3270
|
async function writeCachedGate(absPath, result, opts) {
|
|
3295
3271
|
const nowFn = opts?.now ?? (() => /* @__PURE__ */ new Date());
|
|
@@ -3307,29 +3283,22 @@ async function writeCachedGate(absPath, result, opts) {
|
|
|
3307
3283
|
} catch {
|
|
3308
3284
|
throw new Error(`writeCachedGate: failed to parse frontmatter in ${absPath}`);
|
|
3309
3285
|
}
|
|
3310
|
-
const
|
|
3311
|
-
if (
|
|
3312
|
-
|
|
3313
|
-
const existingParsed = typeof existingCached === "string" ? parseCachedGateString(existingCached) : null;
|
|
3314
|
-
if (existingParsed && JSON.stringify(existingParsed) === JSON.stringify(newResult)) {
|
|
3315
|
-
return;
|
|
3316
|
-
}
|
|
3317
|
-
} catch {
|
|
3318
|
-
}
|
|
3286
|
+
const existing = coerceCachedGate(fm["cached_gate_result"]);
|
|
3287
|
+
if (existing && JSON.stringify(existing) === JSON.stringify(newResult)) {
|
|
3288
|
+
return;
|
|
3319
3289
|
}
|
|
3320
|
-
const cachedStr = serializeCachedGate(newResult);
|
|
3321
3290
|
const newFm = {};
|
|
3322
3291
|
let inserted = false;
|
|
3323
3292
|
for (const [k, v] of Object.entries(fm)) {
|
|
3324
3293
|
if (k === "cached_gate_result") {
|
|
3325
|
-
newFm["cached_gate_result"] =
|
|
3294
|
+
newFm["cached_gate_result"] = newResult;
|
|
3326
3295
|
inserted = true;
|
|
3327
3296
|
} else {
|
|
3328
3297
|
newFm[k] = v;
|
|
3329
3298
|
}
|
|
3330
3299
|
}
|
|
3331
3300
|
if (!inserted) {
|
|
3332
|
-
newFm["cached_gate_result"] =
|
|
3301
|
+
newFm["cached_gate_result"] = newResult;
|
|
3333
3302
|
}
|
|
3334
3303
|
const fmBlock = serializeFrontmatter(newFm);
|
|
3335
3304
|
const newContent = body.length > 0 ? `${fmBlock}
|
|
@@ -3338,24 +3307,31 @@ ${body}` : `${fmBlock}
|
|
|
3338
3307
|
`;
|
|
3339
3308
|
await fs19.writeFile(absPath, newContent, "utf8");
|
|
3340
3309
|
}
|
|
3341
|
-
function
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
function parseCachedGateString(s) {
|
|
3346
|
-
if (!s.startsWith("{")) return null;
|
|
3347
|
-
try {
|
|
3348
|
-
const parsed = yaml2.load(s);
|
|
3349
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
|
|
3350
|
-
const p = parsed;
|
|
3310
|
+
function coerceCachedGate(val) {
|
|
3311
|
+
if (val === void 0 || val === null) return null;
|
|
3312
|
+
if (typeof val === "object" && !Array.isArray(val)) {
|
|
3313
|
+
const c = val;
|
|
3351
3314
|
return {
|
|
3352
|
-
pass: Boolean(
|
|
3353
|
-
failing_criteria: Array.isArray(
|
|
3354
|
-
last_gate_check: String(
|
|
3315
|
+
pass: Boolean(c["pass"]),
|
|
3316
|
+
failing_criteria: Array.isArray(c["failing_criteria"]) ? c["failing_criteria"] : [],
|
|
3317
|
+
last_gate_check: String(c["last_gate_check"] ?? "")
|
|
3355
3318
|
};
|
|
3356
|
-
} catch {
|
|
3357
|
-
return null;
|
|
3358
3319
|
}
|
|
3320
|
+
if (typeof val === "string" && val.startsWith("{")) {
|
|
3321
|
+
try {
|
|
3322
|
+
const parsed = yaml4.load(val, { schema: yaml4.CORE_SCHEMA });
|
|
3323
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
|
|
3324
|
+
const p = parsed;
|
|
3325
|
+
return {
|
|
3326
|
+
pass: Boolean(p["pass"]),
|
|
3327
|
+
failing_criteria: Array.isArray(p["failing_criteria"]) ? p["failing_criteria"] : [],
|
|
3328
|
+
last_gate_check: String(p["last_gate_check"] ?? "")
|
|
3329
|
+
};
|
|
3330
|
+
} catch {
|
|
3331
|
+
return null;
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
return null;
|
|
3359
3335
|
}
|
|
3360
3336
|
|
|
3361
3337
|
// src/commands/gate.ts
|
|
@@ -3366,7 +3342,7 @@ function loadGateBlocks(gatesDocPath) {
|
|
|
3366
3342
|
let match;
|
|
3367
3343
|
while ((match = fenceRe.exec(raw)) !== null) {
|
|
3368
3344
|
const yamlContent = match[1];
|
|
3369
|
-
const parsed =
|
|
3345
|
+
const parsed = yaml5.load(yamlContent);
|
|
3370
3346
|
const block = Array.isArray(parsed) ? parsed[0] : parsed;
|
|
3371
3347
|
if (block && typeof block === "object" && "work_item_type" in block && "transition" in block && "severity" in block && "criteria" in block) {
|
|
3372
3348
|
blocks.push(block);
|
|
@@ -3694,7 +3670,7 @@ async function stampTokensHandler(file, opts, cli) {
|
|
|
3694
3670
|
exitFn(1);
|
|
3695
3671
|
return;
|
|
3696
3672
|
}
|
|
3697
|
-
const existingDraftTokens =
|
|
3673
|
+
const existingDraftTokens = coerceDraftTokens(fm["draft_tokens"]);
|
|
3698
3674
|
const existingLastStamp = existingDraftTokens?.last_stamp ?? null;
|
|
3699
3675
|
const buckets = readLedgerForWorkItem(workItemId, { sprintRunsRoot: cli?.sprintRunsRoot });
|
|
3700
3676
|
if (existingLastStamp && buckets.length > 0) {
|
|
@@ -3730,7 +3706,7 @@ async function stampTokensHandler(file, opts, cli) {
|
|
|
3730
3706
|
if (opts.dryRun) {
|
|
3731
3707
|
stdoutFn(`[dry-run] stamp-tokens would write draft_tokens for ${workItemId}:`);
|
|
3732
3708
|
const draftTokensVal = newFm["draft_tokens"];
|
|
3733
|
-
stdoutFn(` draft_tokens: ${
|
|
3709
|
+
stdoutFn(` draft_tokens: ${JSON.stringify(draftTokensVal)}`);
|
|
3734
3710
|
if (stampError) {
|
|
3735
3711
|
stdoutFn(` stamp_error: "${stampError}"`);
|
|
3736
3712
|
}
|
|
@@ -3769,15 +3745,29 @@ function extractWorkItemId(fm, absPath) {
|
|
|
3769
3745
|
}
|
|
3770
3746
|
return null;
|
|
3771
3747
|
}
|
|
3772
|
-
function
|
|
3748
|
+
function coerceDraftTokens(val) {
|
|
3773
3749
|
if (val == null) return null;
|
|
3774
|
-
if (typeof val
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3750
|
+
if (typeof val === "object" && !Array.isArray(val)) {
|
|
3751
|
+
const o = val;
|
|
3752
|
+
return {
|
|
3753
|
+
input: typeof o["input"] === "number" ? o["input"] : null,
|
|
3754
|
+
output: typeof o["output"] === "number" ? o["output"] : null,
|
|
3755
|
+
cache_creation: typeof o["cache_creation"] === "number" ? o["cache_creation"] : null,
|
|
3756
|
+
cache_read: typeof o["cache_read"] === "number" ? o["cache_read"] : null,
|
|
3757
|
+
model: typeof o["model"] === "string" ? o["model"] : null,
|
|
3758
|
+
last_stamp: typeof o["last_stamp"] === "string" ? o["last_stamp"] : "",
|
|
3759
|
+
sessions: Array.isArray(o["sessions"]) ? o["sessions"] : []
|
|
3760
|
+
};
|
|
3780
3761
|
}
|
|
3762
|
+
if (typeof val === "string") {
|
|
3763
|
+
try {
|
|
3764
|
+
const parsed = JSON.parse(val);
|
|
3765
|
+
return parsed;
|
|
3766
|
+
} catch {
|
|
3767
|
+
return null;
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3770
|
+
return null;
|
|
3781
3771
|
}
|
|
3782
3772
|
function aggregateBuckets(buckets, nowIso) {
|
|
3783
3773
|
let totalInput = 0;
|
|
@@ -3830,12 +3820,9 @@ function buildNewFrontmatter(existingFm, tokens, stampError) {
|
|
|
3830
3820
|
if (stampError) {
|
|
3831
3821
|
newFm["stamp_error"] = stampError;
|
|
3832
3822
|
}
|
|
3833
|
-
newFm["draft_tokens"] =
|
|
3823
|
+
newFm["draft_tokens"] = tokens;
|
|
3834
3824
|
return newFm;
|
|
3835
3825
|
}
|
|
3836
|
-
function serializeDraftTokens(tokens) {
|
|
3837
|
-
return JSON.stringify(tokens);
|
|
3838
|
-
}
|
|
3839
3826
|
function buildSerializedContent(fm, body) {
|
|
3840
3827
|
const fmBlock = serializeFrontmatter(fm);
|
|
3841
3828
|
if (body.length > 0) {
|