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.
@@ -1,6 +1,6 @@
1
1
  {
2
- "cleargate_version": "0.2.0",
3
- "generated_at": "2026-04-19T16:46:17.751Z",
2
+ "cleargate_version": "0.2.1",
3
+ "generated_at": "2026-04-19T18:33:06.178Z",
4
4
  "files": [
5
5
  {
6
6
  "path": ".claude/agents/architect.md",
@@ -46,7 +46,7 @@
46
46
  },
47
47
  {
48
48
  "path": ".claude/agents/reporter.md",
49
- "sha256": "a83497454969e81015e7d0415e86a3bcf48cafb13741ed7f18b603f9e54cfb80",
49
+ "sha256": "a9b7e248fd1baaef7fb0c23d4ba92bc94bcd46e9261a6dabbd183477fab96359",
50
50
  "tier": "agent",
51
51
  "overwrite_policy": "always",
52
52
  "preserve_on_uninstall": false
@@ -81,7 +81,7 @@
81
81
  },
82
82
  {
83
83
  "path": ".claude/skills/flashcard/SKILL.md",
84
- "sha256": "56d6bf3797516ac26ada15876ef2f4cf43842b3af7300abef502a75bd177639e",
84
+ "sha256": "4c0fe5bb74697e1eb1f37203ff44b8e6a800f024a2f8c3fc8542aa206cd2c777",
85
85
  "tier": "skill",
86
86
  "overwrite_policy": "always",
87
87
  "preserve_on_uninstall": false
@@ -151,7 +151,7 @@
151
151
  },
152
152
  {
153
153
  "path": ".cleargate/templates/story.md",
154
- "sha256": "3e722c51f706177f08c16b56ef984090c7841102f70384bb47d5442846991fe8",
154
+ "sha256": "6c32fcabc9332cb928ac3b8fbfe618751b0a4cb05cd0f16463c3c4e22f8a67f1",
155
155
  "tier": "template",
156
156
  "overwrite_policy": "merge-3way",
157
157
  "preserve_on_uninstall": false
package/dist/cli.cjs CHANGED
@@ -33,7 +33,7 @@ var import_commander = require("commander");
33
33
  // package.json
34
34
  var package_default = {
35
35
  name: "cleargate",
36
- version: "0.2.0",
36
+ version: "0.2.1",
37
37
  private: false,
38
38
  type: "module",
39
39
  description: "Planning framework for Claude Code agents \u2014 sprint/epic/story protocol, four-agent loop (architect/developer/qa/reporter), Karpathy-style awareness wiki.",
@@ -541,6 +541,7 @@ function getCodebaseVersion(opts) {
541
541
  var fs4 = __toESM(require("fs/promises"), 1);
542
542
 
543
543
  // src/wiki/parse-frontmatter.ts
544
+ var import_js_yaml = __toESM(require("js-yaml"), 1);
544
545
  function parseFrontmatter(raw) {
545
546
  const lines = raw.split("\n");
546
547
  if (lines[0] !== "---") {
@@ -556,71 +557,45 @@ function parseFrontmatter(raw) {
556
557
  if (closeIdx === -1) {
557
558
  throw new Error("parseFrontmatter: missing closing ---");
558
559
  }
559
- const fmLines = lines.slice(1, closeIdx);
560
+ const yamlText = lines.slice(1, closeIdx).join("\n");
560
561
  const bodyLines = lines.slice(closeIdx + 1);
561
562
  if (bodyLines[0] === "") bodyLines.shift();
562
- const fm = {};
563
- for (const line of fmLines) {
564
- if (line.trim() === "" || line.trim().startsWith("#")) continue;
565
- const colon = line.indexOf(":");
566
- if (colon === -1) continue;
567
- const key = line.slice(0, colon).trim();
568
- const val = line.slice(colon + 1).trim();
569
- if (val === "" || val === "[]") {
570
- fm[key] = [];
571
- continue;
572
- }
573
- if (val.startsWith("[") && val.endsWith("]")) {
574
- const inner = val.slice(1, -1).trim();
575
- if (inner === "") {
576
- fm[key] = [];
577
- continue;
578
- }
579
- fm[key] = inner.split(",").map((s) => s.trim().replace(/^["']|["']$/g, ""));
580
- continue;
581
- }
582
- if (val.startsWith("{")) {
583
- fm[key] = val;
584
- continue;
585
- }
586
- fm[key] = val.replace(/^["']|["']$/g, "");
563
+ const body = bodyLines.join("\n");
564
+ if (yamlText.trim() === "") {
565
+ return { fm: {}, body };
587
566
  }
588
- return { fm, body: bodyLines.join("\n") };
567
+ let parsed;
568
+ try {
569
+ parsed = import_js_yaml.default.load(yamlText, { schema: import_js_yaml.default.CORE_SCHEMA });
570
+ } catch (err) {
571
+ throw new Error(`parseFrontmatter: invalid YAML: ${err.message}`);
572
+ }
573
+ if (parsed === null || parsed === void 0) {
574
+ return { fm: {}, body };
575
+ }
576
+ if (typeof parsed !== "object" || Array.isArray(parsed)) {
577
+ throw new Error("parseFrontmatter: frontmatter is not a YAML mapping");
578
+ }
579
+ return { fm: parsed, body };
589
580
  }
590
581
 
591
582
  // src/lib/frontmatter-yaml.ts
583
+ var import_js_yaml2 = __toESM(require("js-yaml"), 1);
592
584
  function serializeFrontmatter(fm) {
593
- const lines = ["---"];
594
- for (const [key, val] of Object.entries(fm)) {
595
- if (val === null) {
596
- lines.push(`${key}: null`);
597
- } else if (typeof val === "boolean") {
598
- lines.push(`${key}: ${val}`);
599
- } else if (typeof val === "number") {
600
- lines.push(`${key}: ${val}`);
601
- } else if (Array.isArray(val)) {
602
- if (val.length === 0) {
603
- lines.push(`${key}: []`);
604
- } else {
605
- const items = val.map((v) => `"${String(v)}"`).join(", ");
606
- lines.push(`${key}: [${items}]`);
607
- }
608
- } else {
609
- const s = String(val);
610
- if (s.startsWith("{")) {
611
- lines.push(`${key}: ${s}`);
612
- } else {
613
- const needsQuotes = /[:#\[\]{}&*!|>'"%@`\n]/.test(s) || s.trim() !== s || s === "" || s === "null" || s === "true" || s === "false";
614
- if (needsQuotes) {
615
- lines.push(`${key}: "${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`);
616
- } else {
617
- lines.push(`${key}: ${s}`);
618
- }
619
- }
620
- }
621
- }
622
- lines.push("---");
623
- return lines.join("\n");
585
+ if (Object.keys(fm).length === 0) {
586
+ return "---\n---";
587
+ }
588
+ const yamlBody = import_js_yaml2.default.dump(fm, {
589
+ schema: import_js_yaml2.default.CORE_SCHEMA,
590
+ lineWidth: -1,
591
+ noRefs: true,
592
+ noCompatMode: true,
593
+ quotingType: '"',
594
+ forceQuotes: false
595
+ });
596
+ return `---
597
+ ${yamlBody.replace(/\n+$/, "")}
598
+ ---`;
624
599
  }
625
600
  function toIsoSecond(d) {
626
601
  return d.toISOString().replace(/\.\d{3}Z$/, "Z");
@@ -2217,7 +2192,7 @@ function loadWikiPages(wikiRoot) {
2217
2192
  var fs16 = __toESM(require("fs"), 1);
2218
2193
  var path17 = __toESM(require("path"), 1);
2219
2194
  var import_node_child_process3 = require("child_process");
2220
- var import_js_yaml = __toESM(require("js-yaml"), 1);
2195
+ var import_js_yaml3 = __toESM(require("js-yaml"), 1);
2221
2196
 
2222
2197
  // src/lib/work-item-type.ts
2223
2198
  var FM_KEY_MAP = [
@@ -2464,7 +2439,7 @@ function parseCachedGateResult(raw) {
2464
2439
  if (typeof raw === "string") {
2465
2440
  if (!raw.startsWith("{")) return null;
2466
2441
  try {
2467
- const parsed = import_js_yaml.default.load(raw);
2442
+ const parsed = import_js_yaml3.default.load(raw);
2468
2443
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
2469
2444
  const p = parsed;
2470
2445
  return { pass: p["pass"], failing_criteria: p["failing_criteria"], last_gate_check: p["last_gate_check"] };
@@ -2947,15 +2922,23 @@ function emitSummary(driftMap, verbose, stdout) {
2947
2922
  var SESSION_START_MAX_ITEMS = 10;
2948
2923
  var SESSION_START_MAX_CHARS = 400;
2949
2924
  function parseCachedGateResult2(raw) {
2950
- try {
2951
- const parsed = JSON.parse(raw);
2952
- return {
2953
- pass: parsed.pass ?? null,
2954
- failing_criteria: parsed.failing_criteria ?? []
2955
- };
2956
- } catch {
2925
+ if (raw == null) return null;
2926
+ let parsed = null;
2927
+ if (typeof raw === "object" && !Array.isArray(raw)) {
2928
+ parsed = raw;
2929
+ } else if (typeof raw === "string") {
2930
+ try {
2931
+ parsed = JSON.parse(raw);
2932
+ } catch {
2933
+ return null;
2934
+ }
2935
+ } else {
2957
2936
  return null;
2958
2937
  }
2938
+ return {
2939
+ pass: parsed.pass ?? null,
2940
+ failing_criteria: parsed.failing_criteria ?? []
2941
+ };
2959
2942
  }
2960
2943
  async function runSessionStart(cwd, stdout) {
2961
2944
  const pendingSyncDir = path20.join(cwd, ".cleargate", "delivery", "pending-sync");
@@ -2980,9 +2963,7 @@ async function runSessionStart(cwd, stdout) {
2980
2963
  } catch {
2981
2964
  continue;
2982
2965
  }
2983
- const gateRaw = fm["cached_gate_result"];
2984
- if (typeof gateRaw !== "string") continue;
2985
- const gate2 = parseCachedGateResult2(gateRaw);
2966
+ const gate2 = parseCachedGateResult2(fm["cached_gate_result"]);
2986
2967
  if (!gate2 || gate2.pass !== false) continue;
2987
2968
  const idKeys = ["story_id", "epic_id", "proposal_id", "cr_id", "bug_id", "sprint_id"];
2988
2969
  let itemId = "";
@@ -3047,15 +3028,23 @@ async function runPricing(filePath, cwd, stdout, stderr, exit) {
3047
3028
  return;
3048
3029
  }
3049
3030
  const draftTokensRaw = fm["draft_tokens"];
3050
- if (!draftTokensRaw || typeof draftTokensRaw !== "string") {
3031
+ if (!draftTokensRaw) {
3051
3032
  stdout("draft_tokens unpopulated \u2014 run cleargate stamp-tokens first");
3052
3033
  exit(1);
3053
3034
  return;
3054
3035
  }
3055
3036
  let draftTokens;
3056
- try {
3057
- draftTokens = JSON.parse(draftTokensRaw);
3058
- } catch {
3037
+ if (typeof draftTokensRaw === "object" && !Array.isArray(draftTokensRaw)) {
3038
+ draftTokens = draftTokensRaw;
3039
+ } else if (typeof draftTokensRaw === "string") {
3040
+ try {
3041
+ draftTokens = JSON.parse(draftTokensRaw);
3042
+ } catch {
3043
+ stdout("draft_tokens unpopulated \u2014 run cleargate stamp-tokens first");
3044
+ exit(1);
3045
+ return;
3046
+ }
3047
+ } else {
3059
3048
  stdout("draft_tokens unpopulated \u2014 run cleargate stamp-tokens first");
3060
3049
  exit(1);
3061
3050
  return;
@@ -3117,7 +3106,7 @@ async function doctorHandler(flags, cli) {
3117
3106
  // src/commands/gate.ts
3118
3107
  var fs21 = __toESM(require("fs"), 1);
3119
3108
  var path22 = __toESM(require("path"), 1);
3120
- var import_js_yaml3 = __toESM(require("js-yaml"), 1);
3109
+ var import_js_yaml5 = __toESM(require("js-yaml"), 1);
3121
3110
 
3122
3111
  // src/lib/readiness-predicates.ts
3123
3112
  var fs19 = __toESM(require("fs"), 1);
@@ -3445,7 +3434,7 @@ function evalStatusOf(parsed, opts, projectRoot) {
3445
3434
 
3446
3435
  // src/lib/frontmatter-cache.ts
3447
3436
  var fs20 = __toESM(require("fs/promises"), 1);
3448
- var import_js_yaml2 = __toESM(require("js-yaml"), 1);
3437
+ var import_js_yaml4 = __toESM(require("js-yaml"), 1);
3449
3438
  async function readCachedGate(absPath) {
3450
3439
  let raw;
3451
3440
  try {
@@ -3459,20 +3448,7 @@ async function readCachedGate(absPath) {
3459
3448
  } catch {
3460
3449
  return null;
3461
3450
  }
3462
- const cached = fm["cached_gate_result"];
3463
- if (cached === void 0 || cached === null) return null;
3464
- if (typeof cached === "string") {
3465
- return parseCachedGateString(cached);
3466
- }
3467
- if (typeof cached === "object" && !Array.isArray(cached)) {
3468
- const c = cached;
3469
- return {
3470
- pass: Boolean(c["pass"]),
3471
- failing_criteria: Array.isArray(c["failing_criteria"]) ? c["failing_criteria"] : [],
3472
- last_gate_check: String(c["last_gate_check"] ?? "")
3473
- };
3474
- }
3475
- return null;
3451
+ return coerceCachedGate(fm["cached_gate_result"]);
3476
3452
  }
3477
3453
  async function writeCachedGate(absPath, result, opts) {
3478
3454
  const nowFn = opts?.now ?? (() => /* @__PURE__ */ new Date());
@@ -3490,29 +3466,22 @@ async function writeCachedGate(absPath, result, opts) {
3490
3466
  } catch {
3491
3467
  throw new Error(`writeCachedGate: failed to parse frontmatter in ${absPath}`);
3492
3468
  }
3493
- const existingCached = fm["cached_gate_result"];
3494
- if (existingCached !== void 0 && existingCached !== null) {
3495
- try {
3496
- const existingParsed = typeof existingCached === "string" ? parseCachedGateString(existingCached) : null;
3497
- if (existingParsed && JSON.stringify(existingParsed) === JSON.stringify(newResult)) {
3498
- return;
3499
- }
3500
- } catch {
3501
- }
3469
+ const existing = coerceCachedGate(fm["cached_gate_result"]);
3470
+ if (existing && JSON.stringify(existing) === JSON.stringify(newResult)) {
3471
+ return;
3502
3472
  }
3503
- const cachedStr = serializeCachedGate(newResult);
3504
3473
  const newFm = {};
3505
3474
  let inserted = false;
3506
3475
  for (const [k, v] of Object.entries(fm)) {
3507
3476
  if (k === "cached_gate_result") {
3508
- newFm["cached_gate_result"] = cachedStr;
3477
+ newFm["cached_gate_result"] = newResult;
3509
3478
  inserted = true;
3510
3479
  } else {
3511
3480
  newFm[k] = v;
3512
3481
  }
3513
3482
  }
3514
3483
  if (!inserted) {
3515
- newFm["cached_gate_result"] = cachedStr;
3484
+ newFm["cached_gate_result"] = newResult;
3516
3485
  }
3517
3486
  const fmBlock = serializeFrontmatter(newFm);
3518
3487
  const newContent = body.length > 0 ? `${fmBlock}
@@ -3521,24 +3490,31 @@ ${body}` : `${fmBlock}
3521
3490
  `;
3522
3491
  await fs20.writeFile(absPath, newContent, "utf8");
3523
3492
  }
3524
- function serializeCachedGate(result) {
3525
- const criteriaStr = result.failing_criteria.length === 0 ? "[]" : "[" + result.failing_criteria.map((c) => `{id: ${JSON.stringify(c.id)}, detail: ${JSON.stringify(c.detail)}}`).join(", ") + "]";
3526
- return `{pass: ${result.pass}, failing_criteria: ${criteriaStr}, last_gate_check: ${JSON.stringify(result.last_gate_check)}}`;
3527
- }
3528
- function parseCachedGateString(s) {
3529
- if (!s.startsWith("{")) return null;
3530
- try {
3531
- const parsed = import_js_yaml2.default.load(s);
3532
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
3533
- const p = parsed;
3493
+ function coerceCachedGate(val) {
3494
+ if (val === void 0 || val === null) return null;
3495
+ if (typeof val === "object" && !Array.isArray(val)) {
3496
+ const c = val;
3534
3497
  return {
3535
- pass: Boolean(p["pass"]),
3536
- failing_criteria: Array.isArray(p["failing_criteria"]) ? p["failing_criteria"] : [],
3537
- last_gate_check: String(p["last_gate_check"] ?? "")
3498
+ pass: Boolean(c["pass"]),
3499
+ failing_criteria: Array.isArray(c["failing_criteria"]) ? c["failing_criteria"] : [],
3500
+ last_gate_check: String(c["last_gate_check"] ?? "")
3538
3501
  };
3539
- } catch {
3540
- return null;
3541
3502
  }
3503
+ if (typeof val === "string" && val.startsWith("{")) {
3504
+ try {
3505
+ const parsed = import_js_yaml4.default.load(val, { schema: import_js_yaml4.default.CORE_SCHEMA });
3506
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
3507
+ const p = parsed;
3508
+ return {
3509
+ pass: Boolean(p["pass"]),
3510
+ failing_criteria: Array.isArray(p["failing_criteria"]) ? p["failing_criteria"] : [],
3511
+ last_gate_check: String(p["last_gate_check"] ?? "")
3512
+ };
3513
+ } catch {
3514
+ return null;
3515
+ }
3516
+ }
3517
+ return null;
3542
3518
  }
3543
3519
 
3544
3520
  // src/commands/gate.ts
@@ -3549,7 +3525,7 @@ function loadGateBlocks(gatesDocPath) {
3549
3525
  let match;
3550
3526
  while ((match = fenceRe.exec(raw)) !== null) {
3551
3527
  const yamlContent = match[1];
3552
- const parsed = import_js_yaml3.default.load(yamlContent);
3528
+ const parsed = import_js_yaml5.default.load(yamlContent);
3553
3529
  const block = Array.isArray(parsed) ? parsed[0] : parsed;
3554
3530
  if (block && typeof block === "object" && "work_item_type" in block && "transition" in block && "severity" in block && "criteria" in block) {
3555
3531
  blocks.push(block);
@@ -3877,7 +3853,7 @@ async function stampTokensHandler(file, opts, cli) {
3877
3853
  exitFn(1);
3878
3854
  return;
3879
3855
  }
3880
- const existingDraftTokens = parseDraftTokens(fm["draft_tokens"]);
3856
+ const existingDraftTokens = coerceDraftTokens(fm["draft_tokens"]);
3881
3857
  const existingLastStamp = existingDraftTokens?.last_stamp ?? null;
3882
3858
  const buckets = readLedgerForWorkItem(workItemId, { sprintRunsRoot: cli?.sprintRunsRoot });
3883
3859
  if (existingLastStamp && buckets.length > 0) {
@@ -3913,7 +3889,7 @@ async function stampTokensHandler(file, opts, cli) {
3913
3889
  if (opts.dryRun) {
3914
3890
  stdoutFn(`[dry-run] stamp-tokens would write draft_tokens for ${workItemId}:`);
3915
3891
  const draftTokensVal = newFm["draft_tokens"];
3916
- stdoutFn(` draft_tokens: ${String(draftTokensVal)}`);
3892
+ stdoutFn(` draft_tokens: ${JSON.stringify(draftTokensVal)}`);
3917
3893
  if (stampError) {
3918
3894
  stdoutFn(` stamp_error: "${stampError}"`);
3919
3895
  }
@@ -3952,15 +3928,29 @@ function extractWorkItemId(fm, absPath) {
3952
3928
  }
3953
3929
  return null;
3954
3930
  }
3955
- function parseDraftTokens(val) {
3931
+ function coerceDraftTokens(val) {
3956
3932
  if (val == null) return null;
3957
- if (typeof val !== "string") return null;
3958
- try {
3959
- const parsed = JSON.parse(val);
3960
- return parsed;
3961
- } catch {
3962
- return null;
3933
+ if (typeof val === "object" && !Array.isArray(val)) {
3934
+ const o = val;
3935
+ return {
3936
+ input: typeof o["input"] === "number" ? o["input"] : null,
3937
+ output: typeof o["output"] === "number" ? o["output"] : null,
3938
+ cache_creation: typeof o["cache_creation"] === "number" ? o["cache_creation"] : null,
3939
+ cache_read: typeof o["cache_read"] === "number" ? o["cache_read"] : null,
3940
+ model: typeof o["model"] === "string" ? o["model"] : null,
3941
+ last_stamp: typeof o["last_stamp"] === "string" ? o["last_stamp"] : "",
3942
+ sessions: Array.isArray(o["sessions"]) ? o["sessions"] : []
3943
+ };
3963
3944
  }
3945
+ if (typeof val === "string") {
3946
+ try {
3947
+ const parsed = JSON.parse(val);
3948
+ return parsed;
3949
+ } catch {
3950
+ return null;
3951
+ }
3952
+ }
3953
+ return null;
3964
3954
  }
3965
3955
  function aggregateBuckets(buckets, nowIso) {
3966
3956
  let totalInput = 0;
@@ -4013,12 +4003,9 @@ function buildNewFrontmatter(existingFm, tokens, stampError) {
4013
4003
  if (stampError) {
4014
4004
  newFm["stamp_error"] = stampError;
4015
4005
  }
4016
- newFm["draft_tokens"] = serializeDraftTokens(tokens);
4006
+ newFm["draft_tokens"] = tokens;
4017
4007
  return newFm;
4018
4008
  }
4019
- function serializeDraftTokens(tokens) {
4020
- return JSON.stringify(tokens);
4021
- }
4022
4009
  function buildSerializedContent(fm, body) {
4023
4010
  const fmBlock = serializeFrontmatter(fm);
4024
4011
  if (body.length > 0) {