litclaude-ai 0.3.30 → 0.3.31

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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.31 - 2026-07-22 — litgoal lifecycle and visual QA hardening
4
+
5
+ - Made duplicate litgoal creation byte-preserving, required explicit replacement
6
+ for a different active objective, and rejected completion with empty criteria.
7
+ - Made all-pass autoloops complete and disarm durably with idempotent completion
8
+ receipts, repairable ledger writes, and fail-open preservation of corrupt counters.
9
+ - Clarified visual QA backend ownership, authentication boundaries, bounded waits,
10
+ advisory metrics, blocked states, and observed cleanup receipts.
11
+
3
12
  ## 0.3.30 - 2026-07-19 — LitResearch scientific records and public-reader hardening
4
13
 
5
14
  - Added root-owned append-only claim/evidence records, bounded expansion and
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  </p>
10
10
  <p align="center">
11
11
  <img src="https://img.shields.io/badge/npm-litclaude--ai-cb3837" />
12
- <img src="https://img.shields.io/badge/version-0.3.30-2ea44f" />
12
+ <img src="https://img.shields.io/badge/version-0.3.31-2ea44f" />
13
13
  <img src="https://img.shields.io/badge/Claude%20Code-plugin-blueviolet" />
14
14
  <img src="https://img.shields.io/badge/license-MIT-blue" />
15
15
  </p>
@@ -22,10 +22,10 @@
22
22
  > `litclaude@litclaude-ai`, so normal `claude` launches can load the
23
23
  > LitClaude skills and hooks without a long `--plugin-dir` command.
24
24
 
25
- This checkout is prepared as `litclaude-ai@0.3.30` for personal install
25
+ This checkout is prepared as `litclaude-ai@0.3.31` for personal install
26
26
  convenience. The repo can remain quiet; preparing npm package metadata here does
27
27
  not imply public repo promotion, marketplace publication, or advertisement.
28
- Future package releases still require explicit user approval. The v0.3.30 release material strengthens LitResearch with root-owned scientific evidence records, DOI/PDF lifecycle receipts, bounded route coverage, and a connection-pinned public-source reader with inert-content and secret-redaction guarantees. The v0.3.29 release material bundles exact-source `lit-handoff` and `lit-scientific-visualization` skills with Claude-native commands, exact-bare hook invocation, visible LITBURN banners, installed-payload integrity checks, and read-only scientific dependency diagnostics. The v0.3.28 release material requires explicit leading start-work invocations so diagnostic or copied mentions remain inert. The v0.3.27 release material adds a structured, animated five-stage installer with a success or failure receipt while preserving deterministic automation output. The v0.3.26 release material adds adaptive objective-achievable planning, draft-plan review, and orchestration readiness hardening. The v0.3.25 release material aligns portable details, public-read, litgoal status JSON, and evidence wording. The v0.3.24 release material aligns auxiliary skill inventories and advisory probes. The v0.3.23
28
+ Future package releases still require explicit user approval. The v0.3.31 release material hardens litgoal creation, replacement, completion, and autoloop receipts while making visual QA browser ownership, bounded waits, blocked states, and cleanup evidence explicit. The v0.3.30 release material strengthens LitResearch with root-owned scientific evidence records, DOI/PDF lifecycle receipts, bounded route coverage, and a connection-pinned public-source reader with inert-content and secret-redaction guarantees. The v0.3.29 release material bundles exact-source `lit-handoff` and `lit-scientific-visualization` skills with Claude-native commands, exact-bare hook invocation, visible LITBURN banners, installed-payload integrity checks, and read-only scientific dependency diagnostics. The v0.3.28 release material requires explicit leading start-work invocations so diagnostic or copied mentions remain inert. The v0.3.27 release material adds a structured, animated five-stage installer with a success or failure receipt while preserving deterministic automation output. The v0.3.26 release material adds adaptive objective-achievable planning, draft-plan review, and orchestration readiness hardening. The v0.3.25 release material aligns portable details, public-read, litgoal status JSON, and evidence wording. The v0.3.24 release material aligns auxiliary skill inventories and advisory probes. The v0.3.23
29
29
  release materials inject the bundled skill bodies for bare `hyperplan`,
30
30
  `litresearch`, `lit research`, `init-deep`, and explicit `$start-work` prompt-hook routes while
31
31
  preserving the v0.3.19 adversarial planning skill, the v0.3.18 read-only `lit-recap` session recap surface, the v0.3.17 separate-worker native `/goal` launcher, the v0.3.16
@@ -131,7 +131,7 @@ directory, the normal install command works:
131
131
 
132
132
  ```bash
133
133
  cd /tmp
134
- npx --yes litclaude-ai@0.3.30 install
134
+ npx --yes litclaude-ai@0.3.31 install
135
135
  ```
136
136
 
137
137
  Validate the installed plugin:
@@ -144,7 +144,7 @@ The installer also sets Claude Code's `statusLine` command to the packaged
144
144
  LitClaude HUD. A typical no-color render starts like:
145
145
 
146
146
  ```text
147
- [🔥LITCLAUDE v0.3.30] | O4.8 │ ctx [▎░░] 9%/1000k │ 5h [▏░] 4% ↻2h15m │ 1w [▊░] 35% ↻3d6h │ git main +3 ✓
147
+ [🔥LITCLAUDE v0.3.31] | O4.8 │ ctx [▎░░] 9%/1000k │ 5h [▏░] 4% ↻2h15m │ 1w [▊░] 35% ↻3d6h │ git main +3 ✓
148
148
  ```
149
149
 
150
150
  The `↻` suffix is a compact rate-limit reset countdown. It is separated from
package/README_ko-KR.md CHANGED
@@ -9,7 +9,7 @@
9
9
  </p>
10
10
  <p align="center">
11
11
  <img src="https://img.shields.io/badge/npm-litclaude--ai-cb3837" />
12
- <img src="https://img.shields.io/badge/version-0.3.30-2ea44f" />
12
+ <img src="https://img.shields.io/badge/version-0.3.31-2ea44f" />
13
13
  <img src="https://img.shields.io/badge/Claude%20Code-plugin-blueviolet" />
14
14
  <img src="https://img.shields.io/badge/license-MIT-blue" />
15
15
  </p>
@@ -26,11 +26,11 @@
26
26
  > 설치되므로, 매번 긴 `--plugin-dir` 없이 일반 `claude` 실행에서
27
27
  > LitClaude skill과 hook을 불러올 수 있습니다.
28
28
 
29
- 현재 checkout은 `litclaude-ai@0.3.30` 배포 준비용으로 정리되어 있습니다. 목적은
29
+ 현재 checkout은 `litclaude-ai@0.3.31` 배포 준비용으로 정리되어 있습니다. 목적은
30
30
  다른 PC에서도 빠르게 설치하기 위한 개인용 package metadata를 갖추는 것입니다.
31
31
  npm package metadata를 준비했다고 해서 홍보, 공개 저장소 운영, Claude
32
32
  marketplace 등록을 의미하지는 않습니다. 새 버전 배포는 항상 별도의 명시적
33
- 승인 후에 진행합니다. v0.3.30 release material은 root-owned scientific evidence record, DOI/PDF lifecycle receipt, bounded route coverage, connection-pinned public-source reader, inert-content 및 secret-redaction 보장을 포함하는 LitResearch 강화판입니다. v0.3.29 release material은 exact-source `lit-handoff`와 `lit-scientific-visualization` skill, Claude-native command, exact-bare hook invocation, visible LITBURN banner, installed-payload integrity check, read-only scientific dependency diagnostic을 함께 포함합니다. v0.3.28 release material은 start-work 호출을 prompt 선두의 명시적 invocation으로 제한해 진단문이나 복사된 언급을 비활성 상태로 유지합니다. v0.3.27 release material은 success/failure receipt와 deterministic automation output을 갖춘 5-stage animated installer를 추가합니다. v0.3.26 release material은 adaptive objective-achievable planning, draft-plan review, orchestration readiness hardening을 추가합니다. v0.3.25 release material은 portable details, public-read, litgoal status JSON, evidence wording을 정렬합니다. v0.3.24 release material은 auxiliary skill inventory와 advisory probe를 정렬합니다. v0.3.23 release material은 bare `hyperplan`,
33
+ 승인 후에 진행합니다. v0.3.31 release material은 litgoal 생성·교체·완료와 autoloop receipt를 강화하고 visual QA의 browser ownership, bounded wait, blocked state, cleanup evidence를 명확히 합니다. v0.3.30 release material은 root-owned scientific evidence record, DOI/PDF lifecycle receipt, bounded route coverage, connection-pinned public-source reader, inert-content 및 secret-redaction 보장을 포함하는 LitResearch 강화판입니다. v0.3.29 release material은 exact-source `lit-handoff`와 `lit-scientific-visualization` skill, Claude-native command, exact-bare hook invocation, visible LITBURN banner, installed-payload integrity check, read-only scientific dependency diagnostic을 함께 포함합니다. v0.3.28 release material은 start-work 호출을 prompt 선두의 명시적 invocation으로 제한해 진단문이나 복사된 언급을 비활성 상태로 유지합니다. v0.3.27 release material은 success/failure receipt와 deterministic automation output을 갖춘 5-stage animated installer를 추가합니다. v0.3.26 release material은 adaptive objective-achievable planning, draft-plan review, orchestration readiness hardening을 추가합니다. v0.3.25 release material은 portable details, public-read, litgoal status JSON, evidence wording을 정렬합니다. v0.3.24 release material은 auxiliary skill inventory와 advisory probe를 정렬합니다. v0.3.23 release material은 bare `hyperplan`,
34
34
  `litresearch`, `lit research`, `init-deep`, explicit `$start-work` prompt-hook route에
35
35
  bundled skill body를 주입하면서, v0.3.19 adversarial planning
36
36
  skill, v0.3.18 read-only `lit-recap` session recap surface, v0.3.17 별도 worker 기반 native `/goal`
@@ -131,7 +131,7 @@ checkout을 먼저 해석해서 `sh: litclaude-ai: command not found`로 실패
131
131
 
132
132
  ```bash
133
133
  cd /tmp
134
- npx --yes litclaude-ai@0.3.30 install
134
+ npx --yes litclaude-ai@0.3.31 install
135
135
  ```
136
136
 
137
137
  설치 상태를 확인합니다.
@@ -144,7 +144,7 @@ installer는 Claude Code의 `statusLine` command도 packaged LitClaude HUD로
144
144
  설정합니다. 색상을 제거한 예시는 다음처럼 시작합니다.
145
145
 
146
146
  ```text
147
- [🔥LITCLAUDE v0.3.30] | O4.8 │ ctx [▎░░] 9%/1000k │ 5h [▏░] 4% ↻2h15m │ 1w [▊░] 35% ↻3d6h │ git main +3 ✓
147
+ [🔥LITCLAUDE v0.3.31] | O4.8 │ ctx [▎░░] 9%/1000k │ 5h [▏░] 4% ↻2h15m │ 1w [▊░] 35% ↻3d6h │ git main +3 ✓
148
148
  ```
149
149
 
150
150
  `↻` 표시는 rate-limit reset까지 남은 시간을 짧게 보여주는 countdown입니다.
@@ -1,7 +1,9 @@
1
1
  # LitClaude Release Checklist
2
2
 
3
- Status: `litclaude-ai@0.3.30` is the current release candidate — root-owned
4
- scientific evidence records, DOI/PDF lifecycle receipts, bounded route coverage,
3
+ Status: `litclaude-ai@0.3.31` is the current release candidate — guarded litgoal
4
+ creation and replacement, durable idempotent autoloop completion, and explicit
5
+ visual QA backend ownership, bounded waits, blocked states, and cleanup receipts,
6
+ plus root-owned scientific evidence records, DOI/PDF lifecycle receipts, bounded route coverage,
5
7
  connection-pinned public-source reads, inert-content receipts, and deep secret
6
8
  redaction, plus bundled
7
9
  exact-source `lit-handoff` and `lit-scientific-visualization` skills with
@@ -23,9 +25,9 @@ side-effect-free, the launcher starts only a separate Claude Code
23
25
  print/background worker, and the release preserves the Korean polishing
24
26
  command, strict multi-agent review pipeline, fidelity guardrails, package
25
27
  hygiene checks, native route gates, and safe start-work handoff behavior.
26
- `package.json` is aligned to `0.3.30`,
27
- `plugins/litclaude/.claude-plugin/plugin.json` is aligned to `0.3.30`, and the
28
- plugin-local MCP server reports `0.3.30`.
28
+ `package.json` is aligned to `0.3.31`,
29
+ `plugins/litclaude/.claude-plugin/plugin.json` is aligned to `0.3.31`, and the
30
+ plugin-local MCP server reports `0.3.31`.
29
31
 
30
32
  This release carries the v0.2.2 Dynamic workflow hardening surfaces:
31
33
  `/dynamic-workflow`, `workflow-check --json`, native `/goal` fallback guidance,
@@ -69,6 +71,15 @@ Use this track when testing from the current checkout:
69
71
 
70
72
  No npm publication is required for this track.
71
73
 
74
+ ## v0.3.31 Litgoal Lifecycle and Visual QA Gates
75
+
76
+ Before requesting publication approval, confirm duplicate objectives remain
77
+ byte-preserving, different active objectives require explicit replacement, and
78
+ empty criteria cannot complete. Confirm all-pass autoloops complete and disarm
79
+ once with repairable ledger receipts, corrupt counters remain untouched on the
80
+ fail-open path, and visual QA uses only an owned project Playwright setup or an
81
+ explicit current-session Chrome binding with bounded waits and observed cleanup.
82
+
72
83
  ## v0.3.30 LitResearch and Public Reader Gates
73
84
 
74
85
  Before requesting publication approval, confirm the installed LitResearch body
@@ -111,9 +122,9 @@ checkout and from an isolated install of the packed tarball:
111
122
  Before requesting publication approval, confirm these artifacts from the current
112
123
  checkout:
113
124
 
114
- - `package.json` version is `0.3.30`.
115
- - `plugins/litclaude/.claude-plugin/plugin.json` version is `0.3.30`.
116
- - `plugins/litclaude/bin/litclaude-mcp.js` reports server version `0.3.30`.
125
+ - `package.json` version is `0.3.31`.
126
+ - `plugins/litclaude/.claude-plugin/plugin.json` version is `0.3.31`.
127
+ - `plugins/litclaude/bin/litclaude-mcp.js` reports server version `0.3.31`.
117
128
  - Prompt-hook tests cover bundled `SKILL.md` body injection for bare `hyperplan`, `litresearch`, `lit research`, `init-deep`, and explicit leading `$start-work`; diagnostic/copy mentions stay inert while leading natural-language `lit start work` stays BLOCKED.
118
129
  - `lit search` and `lit query` route to `/litclaude:litresearch` without activating on slash mentions, code spans, or non-lit prompts.
119
130
  - Litresearch web lanes require public API/feed preference, validator-first checks, route traces, prompt-injection quarantine, and honest auth/paywall/private-data stop reasons.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "litclaude-ai",
3
- "version": "0.3.30",
3
+ "version": "0.3.31",
4
4
  "description": "Claude Code-native workflow distribution.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "litclaude",
3
3
  "description": "Claude Code-native workflow plugin.",
4
- "version": "0.3.30",
4
+ "version": "0.3.31",
5
5
  "author": {
6
6
  "name": "LitClaude contributors"
7
7
  },
@@ -6,7 +6,7 @@ import { transcriptHasContextPressure } from "../lib/context-pressure.mjs";
6
6
  import { extractMutatedFilePaths } from "../lib/mutated-file-paths.mjs";
7
7
  import { litgoalGoalsPath } from "../lib/litgoal/paths.mjs";
8
8
  import { readLitgoalState } from "../lib/litgoal/state.mjs";
9
- import { evaluateAutoloop, readAutoloopState, writeAutoloopState } from "../lib/litgoal/autoloop.mjs";
9
+ import { completeAutoloopGoal, evaluateAutoloop, readAutoloopState, writeAutoloopState } from "../lib/litgoal/autoloop.mjs";
10
10
  import { buildNativeGoalBindingGuidance, normalizeGoalObjective } from "../lib/native-goal-binding.mjs";
11
11
 
12
12
  const eventName = process.argv[2] ?? "";
@@ -408,6 +408,12 @@ switch (eventName) {
408
408
  console.log(JSON.stringify({ decision: "block", reason: decision.reason }));
409
409
  } else if (decision.action === "cap") {
410
410
  console.log(JSON.stringify({ continue: false, stopReason: decision.stopReason }));
411
+ } else if (
412
+ decision.why !== "kill-switch"
413
+ && (decision.why === "all-criteria-pass"
414
+ || state?.checkpoints?.some(({ generatedBy }) => generatedBy === "stop-hook-autoloop"))
415
+ ) {
416
+ completeAutoloopGoal(cwd);
411
417
  }
412
418
  // action === "allow" (or counter write failed below): emit nothing -> stop is allowed.
413
419
  } catch {
@@ -3,7 +3,7 @@
3
3
  import { readPublicSource } from "../lib/public-source-reader/reader.mjs";
4
4
 
5
5
  const protocolVersion = "2024-11-05";
6
- const serverVersion = "0.3.30";
6
+ const serverVersion = "0.3.31";
7
7
 
8
8
  const publicSourceReadTool = {
9
9
  name: "public_source_read",
@@ -3,9 +3,11 @@
3
3
  // stopping only while it can durably increment this counter under the cap. If the
4
4
  // counter cannot be read or written, the hook fails SAFE (allows stopping) rather
5
5
  // than risk a runaway loop. State lives at `.litclaude/litgoal/autoloop.json`.
6
- import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
6
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
7
7
  import { join } from "node:path";
8
- import { litgoalStateDir } from "./paths.mjs";
8
+ import { appendLitgoalLedger } from "./ledger.mjs";
9
+ import { litgoalGoalsPath, litgoalLedgerPath, litgoalLockDir, litgoalStateDir } from "./paths.mjs";
10
+ import { readLitgoalState, withLitgoalLock, writeLitgoalState } from "./state.mjs";
9
11
 
10
12
  export const litgoalAutoloopPath = (cwd = process.cwd()) => join(litgoalStateDir(cwd), "autoloop.json");
11
13
 
@@ -14,12 +16,25 @@ export const AUTOLOOP_MAX_BLOCKS = 8;
14
16
  export const AUTOLOOP_MAX_MS = 30 * 60 * 1000;
15
17
 
16
18
  export const readAutoloopState = (cwd = process.cwd()) => {
19
+ const path = litgoalAutoloopPath(cwd);
20
+ if (!existsSync(path)) return null;
21
+ let parsed;
17
22
  try {
18
- const parsed = JSON.parse(readFileSync(litgoalAutoloopPath(cwd), "utf8"));
19
- return parsed && typeof parsed === "object" ? parsed : null;
23
+ parsed = JSON.parse(readFileSync(path, "utf8"));
20
24
  } catch {
21
- return null;
25
+ throw new Error("corrupt autoloop state");
22
26
  }
27
+ if (
28
+ !parsed
29
+ || typeof parsed !== "object"
30
+ || !Number.isInteger(parsed.blockCount)
31
+ || parsed.blockCount < 0
32
+ || !Number.isFinite(parsed.firstBlockAt)
33
+ || parsed.firstBlockAt < 0
34
+ ) {
35
+ throw new Error("invalid autoloop state");
36
+ }
37
+ return parsed;
23
38
  };
24
39
 
25
40
  export const writeAutoloopState = (cwd, state) => {
@@ -36,6 +51,50 @@ export const writeAutoloopState = (cwd, state) => {
36
51
  export const resetAutoloopState = (cwd, now = Date.now()) =>
37
52
  writeAutoloopState(cwd, { blockCount: 0, firstBlockAt: now });
38
53
 
54
+ export const completeAutoloopGoal = (cwd = process.cwd()) =>
55
+ withLitgoalLock(litgoalLockDir(cwd), () => {
56
+ const state = readLitgoalState(litgoalGoalsPath(cwd), null);
57
+ if (!state) return { completed: false };
58
+
59
+ let checkpoint = state.checkpoints?.find(({ generatedBy }) => generatedBy === "stop-hook-autoloop");
60
+ const criteria = Array.isArray(state.criteria) ? state.criteria : [];
61
+ const canComplete = state.status === "active"
62
+ && state.autoloop === true
63
+ && criteria.length > 0
64
+ && criteria.every((criterion) => criterion?.status === "pass");
65
+ if (!checkpoint && !canComplete) return { completed: false };
66
+
67
+ if (!checkpoint) {
68
+ const createdAt = new Date().toISOString();
69
+ checkpoint = {
70
+ status: "complete",
71
+ note: "Autoloop completed after all success criteria passed.",
72
+ createdAt,
73
+ generatedBy: "stop-hook-autoloop",
74
+ completionId: `stop-hook-autoloop:${state.createdAt ?? createdAt}`,
75
+ };
76
+ state.status = "complete";
77
+ state.autoloop = false;
78
+ state.checkpoints = [...(state.checkpoints ?? []), checkpoint];
79
+ state.updatedAt = createdAt;
80
+ writeLitgoalState(litgoalGoalsPath(cwd), state);
81
+ }
82
+
83
+ const ledgerPath = litgoalLedgerPath(cwd);
84
+ const entries = existsSync(ledgerPath)
85
+ ? readFileSync(ledgerPath, "utf8").trim().split("\n").filter(Boolean).map((line) => JSON.parse(line))
86
+ : [];
87
+ if (!entries.some((entry) => entry.event === "goal.completed" && entry.completionId === checkpoint.completionId)) {
88
+ appendLitgoalLedger(ledgerPath, {
89
+ event: "goal.completed",
90
+ status: "complete",
91
+ generatedBy: checkpoint.generatedBy,
92
+ completionId: checkpoint.completionId,
93
+ });
94
+ }
95
+ return { completed: true, checkpoint };
96
+ });
97
+
39
98
  // Pure decision function for the Stop hook — kept side-effect-free so it is unit
40
99
  // testable without touching the filesystem or the process. Returns one of:
41
100
  // { action: "allow" } -> let the session stop
@@ -47,8 +106,10 @@ export const evaluateAutoloop = ({ env = {}, state, autoloopState, now = Date.no
47
106
  return { action: "allow", why: "no-active-autoloop-goal" };
48
107
  }
49
108
  const criteria = Array.isArray(state.criteria) ? state.criteria : [];
50
- const remaining = criteria.filter((c) => c && c.status !== "pass");
51
- if (remaining.length === 0) return { action: "allow", why: "all-criteria-pass" };
109
+ const remaining = criteria.length === 0
110
+ ? [{ id: "criteria-required", status: "missing", description: "no success criteria are defined" }]
111
+ : criteria.filter((c) => !c || c.status !== "pass");
112
+ if (criteria.length > 0 && remaining.length === 0) return { action: "allow", why: "all-criteria-pass" };
52
113
 
53
114
  const auto = autoloopState && typeof autoloopState === "object"
54
115
  ? autoloopState
@@ -68,7 +129,9 @@ export const evaluateAutoloop = ({ env = {}, state, autoloopState, now = Date.no
68
129
 
69
130
  const nextCount = blockCount + 1;
70
131
  const list = remaining
71
- .map((c) => ` - ${c.id} [${c.status}]: ${c.description ?? c.scenario ?? ""}`)
132
+ .map((c) => c
133
+ ? ` - ${c.id} [${c.status}]: ${c.description ?? c.scenario ?? ""}`
134
+ : " - criterion-invalid [missing]: invalid criterion")
72
135
  .join("\n");
73
136
  const reason =
74
137
  `LitClaude litgoal still active: "${state.objective}". ${remaining.length} criterion(s) not yet pass:\n${list}\n` +
@@ -12,7 +12,7 @@ import { resetAutoloopState } from "./autoloop.mjs";
12
12
  import { NativeWorkerError, runNativeWorker } from "./native-worker.mjs";
13
13
 
14
14
  const subcommands = [
15
- ["create-goals", "Create durable goal records from a brief."],
15
+ ["create-goals", "Create durable goal records; use --replace for a fresh cycle."],
16
16
  ["status", "Print the current litgoal status."],
17
17
  ["criteria", "List or update success criteria."],
18
18
  ["record-evidence", "Record evidence as JSON."],
@@ -121,7 +121,16 @@ const createGoals = (cwd, args) => {
121
121
  // Opt-in: `--autoloop` arms the LitClaude Stop hook to keep the session running
122
122
  // (a /goal-equivalent loop) until every criterion passes. Default off.
123
123
  const autoloop = hasFlag(args, "--autoloop");
124
+ const replace = hasFlag(args, "--replace");
124
125
  return withLitgoalLock(litgoalLockDir(cwd), () => {
126
+ const existing = readState(cwd);
127
+ if (existing?.objective === brief && !replace) {
128
+ return { ...existing, message: "The same objective already exists; no changes made." };
129
+ }
130
+ if (existing?.status === "active" && existing.objective !== brief && !replace) {
131
+ throw new LitgoalStateError("different active objective exists; use --replace to start a fresh cycle");
132
+ }
133
+
125
134
  const timestamp = nowIso();
126
135
  const state = {
127
136
  version: 1,
@@ -146,6 +155,7 @@ const createGoals = (cwd, args) => {
146
155
  objective: brief,
147
156
  status: state.status,
148
157
  autoloop,
158
+ replaced: Boolean(existing && replace),
149
159
  criteria: state.criteria.map(({ id, status }) => ({ id, status })),
150
160
  });
151
161
  return state;
@@ -196,6 +206,9 @@ const checkpoint = (cwd, args) =>
196
206
  throw new LitgoalCliError("invalid checkpoint status");
197
207
  }
198
208
  const criteria = state.criteria ?? [];
209
+ if (status === "complete" && criteria.length === 0) {
210
+ throw new LitgoalStateError("non-empty criteria must pass before completion");
211
+ }
199
212
  if (status === "complete" && !criteria.every((criterion) => criterion.status === "pass")) {
200
213
  throw new LitgoalStateError("criteria must pass before completion");
201
214
  }
@@ -137,11 +137,38 @@ discard it and regenerate before using it as evidence.
137
137
 
138
138
  ### Web
139
139
 
140
+ #### Browser backend order and ownership
141
+
142
+ Use the project's Playwright setup first: an existing dependency, test config,
143
+ fixtures, browser binary, and command already owned by the project. Do not install
144
+ Playwright, download browsers, or add a replacement runner during QA. If project
145
+ Playwright is absent or cannot cover the required surface, the only fallback is an
146
+ explicitly user-enabled Claude Chrome capability. The capability must be callable in
147
+ the current session through an observed tool or binding; never infer it from
148
+ documentation, a binary on `PATH`, a browser window, or a past session.
149
+
150
+ Reuse only a current session binding or a healthy server with verified PID, port, and
151
+ command ownership before connection. Do not adopt an unknown process or create a
152
+ cross-repo daemon. There is no cookie or profile sharing, automatic authenticated
153
+ persistence, dependency install, or host config mutation. No dependency install or
154
+ host config mutation is allowed. Do not copy browser profile directories, cookies,
155
+ local storage, tokens, or credentials between backends. If an authenticated state is
156
+ required but no user-approved current-session binding provides it, stop rather than
157
+ weakening this boundary.
158
+
159
+ Diff ratios, similarity scores, timings, and coverage counts are evidence metrics.
160
+ These metrics are advisory unless hardened by a checked-in threshold and a
161
+ deterministic project test. They direct review but cannot independently produce PASS.
162
+ Bound navigation, capture, and server-health waits with explicit timeouts. On
163
+ unavailable capability, ownership ambiguity, or timeout, report `BLOCKED:` with the
164
+ attempted backend and reason, then include a cleanup receipt listing stopped processes,
165
+ closed session bindings, removed temporary captures, and any intentionally retained
166
+ evidence. Never report cleanup that was not observed.
167
+
140
168
  1. Capture a REFERENCE image: the user's mock/target, or a known-good baseline.
141
169
  Save as PNG.
142
170
  2. Capture the ACTUAL rendered screenshot at the same viewport size using the
143
- project's browser tooling (Claude Code's browser/Playwright tooling). Save as
144
- PNG.
171
+ selected backend above. Save as PNG.
145
172
  3. Run the diff and keep the JSON:
146
173
 
147
174
  ```