@litfamily/litclaude 1.0.6 → 1.0.8

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.
Files changed (56) hide show
  1. package/CHANGELOG.md +13 -7
  2. package/README.md +18 -38
  3. package/README_ko-KR.md +18 -37
  4. package/docs/assets/cover-motion.webp +0 -0
  5. package/docs/assets/readme/badge-version.svg +1 -1
  6. package/docs/hooks.md +10 -10
  7. package/docs/privacy.md +12 -17
  8. package/package.json +3 -1
  9. package/plugins/litclaude/.claude-plugin/plugin.json +1 -1
  10. package/plugins/litclaude/bin/litclaude-hook.js +71 -65
  11. package/plugins/litclaude/bin/litclaude-hud.js +5 -1
  12. package/plugins/litclaude/bin/litclaude-mcp.js +1 -1
  13. package/plugins/litclaude/commands/lit-handoff.md +3 -3
  14. package/plugins/litclaude/commands/lit-scientific-visualization.md +2 -2
  15. package/plugins/litclaude/hooks/hooks.json +4 -4
  16. package/plugins/litclaude/lib/canonical-skill-catalog.mjs +0 -1
  17. package/plugins/litclaude/lib/canonical-skill-resources.mjs +6 -28
  18. package/plugins/litclaude/lib/hud-accent-palette.mjs +2 -2
  19. package/plugins/litclaude/lib/hud-ignition.mjs +74 -0
  20. package/plugins/litclaude/lib/lit-plan-persistence.mjs +2 -1
  21. package/plugins/litclaude/lib/litgoal/paths.mjs +10 -8
  22. package/plugins/litclaude/lib/project-state-root.mjs +22 -0
  23. package/plugins/litclaude/lib/rules/session-state.mjs +2 -1
  24. package/plugins/litclaude/lib/start-work-cli.mjs +3 -3
  25. package/plugins/litclaude/lib/wikify-knowledge-cli.mjs +3 -1
  26. package/plugins/litclaude/lib/wikify-knowledge.mjs +6 -5
  27. package/plugins/litclaude/scripts/scaffold-plan.mjs +9 -3
  28. package/plugins/litclaude/skills/lit-handoff/SKILL.md +2 -2
  29. package/plugins/litclaude/skills/lit-scientific-visualization/SKILL.md +3 -3
  30. package/plugins/litclaude/skills/lit-team/scripts/team.mjs +4 -3
  31. package/plugins/litclaude/skills/litwork/SKILL.md +2 -2
  32. package/scripts/prepublish-test-gate.mjs +26 -0
  33. package/scripts/qa-real-surface-lib.mjs +3 -2
  34. package/scripts/strip-ansi.mjs +1 -0
  35. package/plugins/litclaude/lib/skill-loop/apply.mjs +0 -391
  36. package/plugins/litclaude/lib/skill-loop/authority.mjs +0 -129
  37. package/plugins/litclaude/lib/skill-loop/cli.mjs +0 -67
  38. package/plugins/litclaude/lib/skill-loop/common.mjs +0 -280
  39. package/plugins/litclaude/lib/skill-loop/config.mjs +0 -91
  40. package/plugins/litclaude/lib/skill-loop/curator.mjs +0 -670
  41. package/plugins/litclaude/lib/skill-loop/hook-runtime.mjs +0 -188
  42. package/plugins/litclaude/lib/skill-loop/hook-state.mjs +0 -484
  43. package/plugins/litclaude/lib/skill-loop/ledger.mjs +0 -834
  44. package/plugins/litclaude/lib/skill-loop/mutation-transaction.mjs +0 -529
  45. package/plugins/litclaude/lib/skill-loop/proposal-reader.mjs +0 -26
  46. package/plugins/litclaude/lib/skill-loop/proposal-record.mjs +0 -104
  47. package/plugins/litclaude/lib/skill-loop/proposal-store.mjs +0 -187
  48. package/plugins/litclaude/lib/skill-loop/proposals.mjs +0 -26
  49. package/plugins/litclaude/lib/skill-loop/review-runner.mjs +0 -13
  50. package/plugins/litclaude/lib/skill-loop/review-transaction.mjs +0 -358
  51. package/plugins/litclaude/lib/skill-loop/review.mjs +0 -223
  52. package/plugins/litclaude/lib/skill-loop/usage.mjs +0 -218
  53. package/plugins/litclaude/lib/skill-observer.mjs +0 -693
  54. package/plugins/litclaude/skills/skill-observer/SKILL.md +0 -155
  55. package/plugins/litclaude/skills/skill-observer/references/review-contract.md +0 -77
  56. package/plugins/litclaude/skills/skill-observer/scripts/skill-loop.mjs +0 -10
@@ -23,6 +23,7 @@ import { isAbsolute, join, relative, resolve } from "node:path";
23
23
  import { acquireOwnerLock, OwnerLockError, releaseOwnerLock } from "../owner-lock.mjs";
24
24
  import { POST_COMPACT_REINJECTION_BUDGET } from "./constants.mjs";
25
25
  import { pathIdentity, samePathIdentity } from "../secure-path-read.mjs";
26
+ import { resolveProjectStateRoot } from "../project-state-root.mjs";
26
27
 
27
28
  const MAX_TRACKED_KEYS = 500;
28
29
  const MAX_STATE_BYTES = 256 * 1024;
@@ -42,7 +43,7 @@ const isSameOrChildPath = (parentPath, childPath) => {
42
43
  const emptyState = () => ({ injected: [], postCompactCount: 0 });
43
44
 
44
45
  const safeStateDirectory = (projectRoot, { create }) => {
45
- const root = resolve(projectRoot);
46
+ const root = resolveProjectStateRoot(projectRoot);
46
47
  const rootStats = lstatSync(root);
47
48
  if (rootStats.isSymbolicLink() || !rootStats.isDirectory()) throw new Error("unsafe project root");
48
49
  const realRoot = realpathSync.native(root);
@@ -1,4 +1,4 @@
1
- import { resolve } from "node:path";
1
+ import { resolveProjectStateRoot } from "./project-state-root.mjs";
2
2
  import {
3
3
  cancelStartWork,
4
4
  completeStartWork,
@@ -141,7 +141,7 @@ const emitError = (io, error, command) => {
141
141
  return status;
142
142
  };
143
143
 
144
- export const runStartWorkCli = (argv, io = { stdout: process.stdout, stderr: process.stderr }, defaultRoot = process.cwd()) => {
144
+ export const runStartWorkCli = (argv, io = { stdout: process.stdout, stderr: process.stderr }, defaultRoot = resolveProjectStateRoot()) => {
145
145
  const [command, ...args] = argv;
146
146
  if (!command || command === "--help" || command === "-h") {
147
147
  io.stdout.write(helpText);
@@ -149,7 +149,7 @@ export const runStartWorkCli = (argv, io = { stdout: process.stdout, stderr: pro
149
149
  }
150
150
  try {
151
151
  assertExactOptions(command, args);
152
- const root = resolve(value(args, "--root") ?? defaultRoot);
152
+ const root = resolveProjectStateRoot(value(args, "--root") ?? defaultRoot);
153
153
  const result = execute(root, command, args);
154
154
  io.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
155
155
  return 0;
@@ -1,4 +1,6 @@
1
1
  import { resolve } from "node:path";
2
+
3
+ import { resolveProjectStateRoot } from "./project-state-root.mjs";
2
4
  import {
3
5
  captureKnowledgeEvent,
4
6
  queryKnowledge,
@@ -116,7 +118,7 @@ const emitError = (io, error, command, outputJson) => {
116
118
  export const runWikifyKnowledgeCli = (
117
119
  argv,
118
120
  io = { stdout: process.stdout, stderr: process.stderr },
119
- defaultRoot = process.cwd(),
121
+ defaultRoot = resolveProjectStateRoot(),
120
122
  ) => {
121
123
  const [command, ...args] = argv;
122
124
  if (!command || command === "--help" || command === "-h") {
@@ -25,6 +25,7 @@ import {
25
25
  } from "./secure-path-read.mjs";
26
26
  import { decodeStrictUtf8, parseStrictJson } from "./strict-json.mjs";
27
27
  import { containsSecret, URI_USERINFO_PATTERN } from "./secret-shapes.mjs";
28
+ import { resolveProjectStateRoot } from "./project-state-root.mjs";
28
29
 
29
30
  const MAX_LEDGER_BYTES = 4 * 1024 * 1024;
30
31
  const MAX_TEXT_BYTES = 512;
@@ -116,7 +117,7 @@ const deterministicId = (kind, text, provenance, evidence) => {
116
117
  };
117
118
 
118
119
  const canonicalRoot = (root) => {
119
- const candidate = resolve(root ?? process.cwd());
120
+ const candidate = resolveProjectStateRoot(root ?? process.cwd());
120
121
  try {
121
122
  const details = lstatSync(candidate);
122
123
  if (details.isSymbolicLink() || !details.isDirectory()) throw new Error("invalid root");
@@ -137,7 +138,7 @@ const ensurePrivateDirectory = (path) => {
137
138
  mkdirSync(path, { mode: 0o700 });
138
139
  };
139
140
 
140
- export const knowledgeDirectory = (root = process.cwd()) => {
141
+ export const knowledgeDirectory = (root = resolveProjectStateRoot()) => {
141
142
  const canonical = canonicalRoot(root);
142
143
  const litDirectory = join(canonical, ".litclaude");
143
144
  const directory = join(litDirectory, "knowledge");
@@ -150,8 +151,8 @@ export const knowledgeDirectory = (root = process.cwd()) => {
150
151
  }
151
152
  return directory;
152
153
  };
153
- export const knowledgeClaimsPath = (root = process.cwd()) => join(knowledgeDirectory(root), "claims.jsonl");
154
- export const knowledgeSettingsPath = (root = process.cwd()) => join(knowledgeDirectory(root), "settings.json");
154
+ export const knowledgeClaimsPath = (root = resolveProjectStateRoot()) => join(knowledgeDirectory(root), "claims.jsonl");
155
+ export const knowledgeSettingsPath = (root = resolveProjectStateRoot()) => join(knowledgeDirectory(root), "settings.json");
155
156
 
156
157
  const prepareKnowledgeDirectory = (root) => {
157
158
  const canonical = canonicalRoot(root);
@@ -510,7 +511,7 @@ const normalizeEvent = (event, surface) => {
510
511
  };
511
512
  };
512
513
 
513
- export const readCaptureSettings = (root = process.cwd()) => {
514
+ export const readCaptureSettings = (root = resolveProjectStateRoot()) => {
514
515
  return withClaimsLock(root, (directory) => {
515
516
  const path = join(directory, "settings.json");
516
517
  const snapshot = assertRegularStateFile(path);
@@ -30,6 +30,7 @@ import { dirname, isAbsolute, join, relative, resolve } from "node:path";
30
30
  import { fileURLToPath } from "node:url";
31
31
 
32
32
  import { parsePlanTaskRows, planLinesOutsideFences } from "../lib/plan-task-rows.mjs";
33
+ import { resolveProjectStateRoot } from "../lib/project-state-root.mjs";
33
34
  import { readRegularStable, samePathIdentity } from "../lib/secure-path-read.mjs";
34
35
 
35
36
  /** Canonical plan section headers, in order. */
@@ -107,8 +108,9 @@ export function parseArgs(argv) {
107
108
 
108
109
  /** Confine a write to `.litclaude/drafts/` or `plans/`, `.md` only. */
109
110
  export function resolveSafePlanPath(cwd, relPath) {
110
- const resolved = resolve(cwd, relPath);
111
- const rel = relative(cwd, resolved);
111
+ const workspaceRoot = resolveProjectStateRoot(cwd);
112
+ const resolved = resolve(workspaceRoot, relPath);
113
+ const rel = relative(workspaceRoot, resolved);
112
114
  if (rel.startsWith("..") || isAbsolute(rel)) {
113
115
  throw new Error(`refused: path escapes the workspace root: ${relPath}`);
114
116
  }
@@ -804,6 +806,7 @@ export async function writeGuarded(
804
806
  onParentState,
805
807
  } = {},
806
808
  ) {
809
+ cwd = resolveProjectStateRoot(cwd);
807
810
  const target = resolveSafePlanPath(cwd, relPath);
808
811
  const ownInterruption = interruption ?? createInterruptionContext();
809
812
  let parentState;
@@ -874,6 +877,7 @@ export async function writeGuarded(
874
877
  }
875
878
 
876
879
  export async function scaffold(cwd, { slug, intent, reset = false, force = false, draftOnly = false, reviewRequired = false }) {
880
+ cwd = resolveProjectStateRoot(cwd);
877
881
  const interruption = createInterruptionContext();
878
882
  const created = [];
879
883
  const parentStates = new Set();
@@ -1007,6 +1011,7 @@ export function checkPlanStructure(text) {
1007
1011
  }
1008
1012
 
1009
1013
  async function runCheck(checkPath, cwd) {
1014
+ cwd = resolveProjectStateRoot(cwd);
1010
1015
  const target = resolveSafePlanPath(cwd, checkPath);
1011
1016
  const read = readRegularStable(resolve(cwd), target, undefined, { maxBytes: MAX_PLAN_BYTES });
1012
1017
  if (read.failure) {
@@ -1023,7 +1028,8 @@ async function runCheck(checkPath, cwd) {
1023
1028
  return 1;
1024
1029
  }
1025
1030
 
1026
- export async function runScaffoldPlanCli(argv = process.argv, cwd = process.cwd()) {
1031
+ export async function runScaffoldPlanCli(argv = process.argv, cwd = resolveProjectStateRoot()) {
1032
+ cwd = resolveProjectStateRoot(cwd);
1027
1033
  const parsed = parseArgs(argv);
1028
1034
  if (parsed.mode === "check") {
1029
1035
  return runCheck(parsed.checkPath, cwd);
@@ -17,7 +17,7 @@ verdicts: [PASS, DEGRADED, FAIL, BLOCKED]
17
17
 
18
18
  | Field | Contract | Evidence |
19
19
  | --- | --- | --- |
20
- | activation | Begin the user-visible response with exactly `▲ LIT · lit-handoff`. | First response line and route receipt. |
20
+ | activation | Begin the user-visible response with exactly `🔥 **LIT IGNITED · lit-handoff** 🔥`. | First response line and route receipt. |
21
21
  | canonical source | Read the vendored handoff reference completely before acting: `SKILL.md` in the canonical `../../vendor/handoff/` directory. | Source path and destination decision. |
22
22
  | completion | Create or update the correct continuation packet without exposing secrets. | Artifact path, checks, and `PASS`, `FAIL`, or `BLOCKED`. |
23
23
 
@@ -39,7 +39,7 @@ verdicts: [PASS, DEGRADED, FAIL, BLOCKED]
39
39
 
40
40
  ## #contract.procedure
41
41
 
42
- 1. Print `▲ LIT · lit-handoff` as the first visible line.
42
+ 1. Print `🔥 **LIT IGNITED · lit-handoff** 🔥` as the first visible line.
43
43
  2. Resolve every linked resource relative to this loaded `SKILL.md` through Claude Code's Skill resource root, never relative to the process cwd. For the exact-bare hook route, use the absolute installed source root injected by the hook. Never assume `~/skills` exists after package installation.
44
44
  3. Read the vendored handoff reference `SKILL.md` in full. Follow every destination, stale-state, secret-redaction, and verification rule in that source without abbreviating it.
45
45
  4. Inspect existing handoff files and live ledgers or JSONL surfaces before choosing the destination. Prefer live operational truth over stale prose.
@@ -17,7 +17,7 @@ verdicts: [PASS, DEGRADED, FAIL, BLOCKED]
17
17
 
18
18
  | Field | Contract | Evidence |
19
19
  | --- | --- | --- |
20
- | activation | Begin the response with the exact banner `▲ LIT · lit-scientific-visualization`. | First visible response line. |
20
+ | activation | Begin the response with the exact probe `🔥 **LIT IGNITED · lit-scientific-visualization** 🔥`. | First visible response line. |
21
21
  | canonical source | Read `../../vendor/scientific-visualization/SKILL.md` completely before plotting. | Source-root receipt. |
22
22
  | runtime | Preflight Python and plotting dependencies without installing them. | PASS or DEGRADED doctor output. |
23
23
  | completion | Produce and inspect the requested figure artifacts. | Files, dimensions, formats, DPI, and visual QA. |
@@ -35,14 +35,14 @@ verdicts: [PASS, DEGRADED, FAIL, BLOCKED]
35
35
  | --- | --- | --- |
36
36
  | direct-skill | Claude Code loads `Skill(lit-scientific-visualization)`. | Explicit user intent is required; there is no generic visualization auto-trigger. |
37
37
  | command-routed | `/litclaude:lit-scientific-visualization` is invoked. | Follow the command and this full adapter. |
38
- | hook-routed | The complete UserPromptSubmit prompt is exactly `lit-scientific-visualization` after outer whitespace. | Show the micro mark and discipline as a hook `systemMessage`, inject this adapter and the complete canonical source, and keep all mixed or near-miss prompts inert. |
38
+ | hook-routed | The complete UserPromptSubmit prompt is exactly `lit-scientific-visualization` after outer whitespace. | Show `🔥 LIT IGNITED · lit-scientific-visualization 🔥` in the hook `systemMessage`, inject this adapter and the complete canonical source, and keep all mixed or near-miss prompts inert. |
39
39
  | core-ready | Python and matplotlib are available. | Use packaged scripts and verify real exports. |
40
40
  | degraded | Python or matplotlib is unavailable. | Report `DEGRADED:` with the missing capability; do not claim artifact creation. |
41
41
  | optional-degraded | seaborn, pandas, scipy, plotly, kaleido, numpy, or MDAnalysis is missing. | Use a valid core fallback or report the specific blocked workflow. |
42
42
 
43
43
  ## #contract.procedure
44
44
 
45
- 1. Begin the reply with the exact banner `▲ LIT · lit-scientific-visualization` on its own line. The exact-bare UserPromptSubmit route renders the micro mark and `litclaude · lit-scientific-visualization` as its visible hook `systemMessage`; the probe is model-emitted exactly once; do not treat generic visualization prose as equivalent invocation.
45
+ 1. Begin the reply with the exact probe `🔥 **LIT IGNITED · lit-scientific-visualization** 🔥` on its own line. The exact-bare UserPromptSubmit route renders `🔥 LIT IGNITED · lit-scientific-visualization 🔥` as its plain-text hook `systemMessage`; the model probe appears exactly once. Do not treat generic visualization prose as equivalent invocation.
46
46
  2. Resolve every linked resource relative to this loaded `SKILL.md` through Claude Code's Skill resource root, never relative to the process cwd. Never hard-code `~/skills` or the source checkout.
47
47
  3. Read the [canonical `SKILL.md`](../../vendor/scientific-visualization/SKILL.md) completely, then read only the referenced source files needed for the requested figure. Preserve every restraint, including `rcparams()`, scatter-only semantics, journal sizing, legend treatment, grids, fonts, layout, and export requirements.
48
48
  4. Resolve the [bundled doctor](../../bin/litclaude-scientific-visualization-doctor.js) as an installed Skill resource and invoke its absolute path with Node, or use `litclaude doctor`; never run a cwd-relative shell path. Never install Python, matplotlib, or optional packages silently. Ask before any dependency or environment mutation.
@@ -38,6 +38,7 @@ import { isAbsolute, join, relative, resolve } from "node:path";
38
38
  import { pathToFileURL } from "node:url";
39
39
 
40
40
  import { acquireOwnerLock, OwnerLockError, releaseOwnerLock } from "../../../lib/owner-lock.mjs";
41
+ import { resolveProjectStateRoot } from "../../../lib/project-state-root.mjs";
41
42
  import { pathIdentity, samePathIdentity } from "../../../lib/secure-path-read.mjs";
42
43
 
43
44
  const MIN_MEMBERS = 2;
@@ -61,7 +62,7 @@ const requireTeamId = (teamId) => {
61
62
  return teamId;
62
63
  };
63
64
 
64
- export const teamsRoot = (cwd = process.cwd()) => join(resolve(cwd), ".litclaude", "teams");
65
+ export const teamsRoot = (cwd = resolveProjectStateRoot()) => join(resolveProjectStateRoot(cwd), ".litclaude", "teams");
65
66
  export const teamDir = (cwd, teamId) => join(teamsRoot(cwd), requireTeamId(teamId));
66
67
  export const teamStatePath = (cwd, teamId) => join(teamDir(cwd, teamId), "team.json");
67
68
 
@@ -87,7 +88,7 @@ const isSameOrChildPath = (parentPath, childPath) => {
87
88
  };
88
89
 
89
90
  const safeDirectory = (cwd, components, { create }) => {
90
- const root = resolve(cwd);
91
+ const root = resolveProjectStateRoot(cwd);
91
92
  const rootStats = lstatSync(root);
92
93
  if (rootStats.isSymbolicLink() || !rootStats.isDirectory()) throw new TeamError("workspace root is not a safe directory", 65);
93
94
  const realRoot = realpathSync.native(root);
@@ -379,7 +380,7 @@ const archive = (cwd, args) => {
379
380
 
380
381
  const commands = { init, "add-member": addMember, prompt, report, block, status, archive };
381
382
 
382
- export const runTeamCli = (argv, io = { stdout: process.stdout, stderr: process.stderr }, cwd = process.cwd()) => {
383
+ export const runTeamCli = (argv, io = { stdout: process.stdout, stderr: process.stderr }, cwd = resolveProjectStateRoot()) => {
383
384
  const [command, ...rest] = argv;
384
385
  if (!command || command === "--help" || command === "-h") {
385
386
  io.stdout.write(`Usage: team.mjs <${Object.keys(commands).join("|")}> [...args]\n`);
@@ -583,8 +583,8 @@ history-safety detail when the operation grows past a single commit.
583
583
 
584
584
  ## Output discipline
585
585
 
586
- - First line, literally: `▲ LIT · litwork`, on its own line, before
587
- anything else, exactly once. The harness renders its mark separately; do not repeat it.
586
+ - First line, literally: `🔥 **LIT IGNITED · litwork** 🔥`, on its own line, before
587
+ anything else, exactly once. The harness shows its plain-text status mark separately; do not draw ASCII art or add a second probe line.
588
588
  - After bootstrap in reader mode: the current result, decision, or next required
589
589
  action. Keep the notepad path internal unless the request asks for it.
590
590
  - During execution in reader mode, surface only a current result, material
@@ -0,0 +1,26 @@
1
+ import { spawnSync } from "node:child_process";
2
+
3
+ const env = Object.fromEntries(
4
+ Object.entries(process.env).filter(([key]) => key.toLowerCase() !== "npm_config_dry_run"),
5
+ );
6
+ const npmExecPath = process.env.npm_execpath;
7
+ const result = npmExecPath
8
+ ? spawnSync(process.execPath, [npmExecPath, "test"], { env, stdio: "inherit" })
9
+ : spawnSync(process.platform === "win32" ? "npm.cmd" : "npm", ["test"], {
10
+ env,
11
+ shell: process.platform === "win32",
12
+ stdio: "inherit",
13
+ });
14
+
15
+ if (result.error) {
16
+ console.error(`prepublish test gate could not start npm test: ${result.error.message}`);
17
+ process.exitCode = 1;
18
+ } else if (result.signal) {
19
+ console.error(`prepublish test gate interrupted by ${result.signal}`);
20
+ process.exitCode = 1;
21
+ } else if (Number.isInteger(result.status)) {
22
+ process.exitCode = result.status;
23
+ } else {
24
+ console.error("prepublish test gate received a non-integer npm test exit status");
25
+ process.exitCode = 1;
26
+ }
@@ -25,6 +25,7 @@ import { tmpdir } from "node:os";
25
25
  import { join, resolve } from "node:path";
26
26
  import { fileURLToPath, pathToFileURL } from "node:url";
27
27
  import { deflateSync } from "node:zlib";
28
+ import { stripAnsi } from "./strip-ansi.mjs";
28
29
 
29
30
  export const repoRoot = resolve(fileURLToPath(new URL("..", import.meta.url)));
30
31
 
@@ -606,8 +607,8 @@ function phaseCorrect(phase, run) {
606
607
  if (phase.id === "session_start") return context.startsWith("LitClaude rules loaded for ");
607
608
  if (phase.id === "s1_activation") {
608
609
  return /LITWORK MODE ENABLED/u.test(context)
609
- && /litclaude · lit-loop/u.test(run.parsed.systemMessage ?? "")
610
- && /▲ LIT · lit-loop/u.test(context);
610
+ && /🔥 LIT IGNITED · lit-loop 🔥/u.test(stripAnsi(run.parsed.systemMessage ?? ""))
611
+ && /🔥 \*\*LIT IGNITED · lit-loop\*\* 🔥/u.test(context);
611
612
  }
612
613
  return run.parsed.systemMessage === undefined
613
614
  && context === "LitClaude prompt hook checked: no workflow activation.";
@@ -0,0 +1 @@
1
+ export { stripVTControlCharacters as stripAnsi } from "node:util";