@litfamily/litclaude 1.0.6 → 1.0.7

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 (45) hide show
  1. package/CHANGELOG.md +5 -5
  2. package/README.md +11 -38
  3. package/README_ko-KR.md +11 -37
  4. package/docs/assets/cover-motion.webp +0 -0
  5. package/docs/assets/readme/badge-version.svg +1 -1
  6. package/docs/privacy.md +12 -17
  7. package/package.json +3 -1
  8. package/plugins/litclaude/.claude-plugin/plugin.json +1 -1
  9. package/plugins/litclaude/bin/litclaude-hook.js +20 -57
  10. package/plugins/litclaude/bin/litclaude-mcp.js +1 -1
  11. package/plugins/litclaude/hooks/hooks.json +4 -4
  12. package/plugins/litclaude/lib/canonical-skill-catalog.mjs +0 -1
  13. package/plugins/litclaude/lib/canonical-skill-resources.mjs +5 -27
  14. package/plugins/litclaude/lib/lit-plan-persistence.mjs +2 -1
  15. package/plugins/litclaude/lib/litgoal/paths.mjs +10 -8
  16. package/plugins/litclaude/lib/project-state-root.mjs +22 -0
  17. package/plugins/litclaude/lib/rules/session-state.mjs +2 -1
  18. package/plugins/litclaude/lib/start-work-cli.mjs +3 -3
  19. package/plugins/litclaude/lib/wikify-knowledge-cli.mjs +3 -1
  20. package/plugins/litclaude/lib/wikify-knowledge.mjs +6 -5
  21. package/plugins/litclaude/scripts/scaffold-plan.mjs +9 -3
  22. package/plugins/litclaude/skills/lit-team/scripts/team.mjs +4 -3
  23. package/scripts/prepublish-test-gate.mjs +26 -0
  24. package/plugins/litclaude/lib/skill-loop/apply.mjs +0 -391
  25. package/plugins/litclaude/lib/skill-loop/authority.mjs +0 -129
  26. package/plugins/litclaude/lib/skill-loop/cli.mjs +0 -67
  27. package/plugins/litclaude/lib/skill-loop/common.mjs +0 -280
  28. package/plugins/litclaude/lib/skill-loop/config.mjs +0 -91
  29. package/plugins/litclaude/lib/skill-loop/curator.mjs +0 -670
  30. package/plugins/litclaude/lib/skill-loop/hook-runtime.mjs +0 -188
  31. package/plugins/litclaude/lib/skill-loop/hook-state.mjs +0 -484
  32. package/plugins/litclaude/lib/skill-loop/ledger.mjs +0 -834
  33. package/plugins/litclaude/lib/skill-loop/mutation-transaction.mjs +0 -529
  34. package/plugins/litclaude/lib/skill-loop/proposal-reader.mjs +0 -26
  35. package/plugins/litclaude/lib/skill-loop/proposal-record.mjs +0 -104
  36. package/plugins/litclaude/lib/skill-loop/proposal-store.mjs +0 -187
  37. package/plugins/litclaude/lib/skill-loop/proposals.mjs +0 -26
  38. package/plugins/litclaude/lib/skill-loop/review-runner.mjs +0 -13
  39. package/plugins/litclaude/lib/skill-loop/review-transaction.mjs +0 -358
  40. package/plugins/litclaude/lib/skill-loop/review.mjs +0 -223
  41. package/plugins/litclaude/lib/skill-loop/usage.mjs +0 -218
  42. package/plugins/litclaude/lib/skill-observer.mjs +0 -693
  43. package/plugins/litclaude/skills/skill-observer/SKILL.md +0 -155
  44. package/plugins/litclaude/skills/skill-observer/references/review-contract.md +0 -77
  45. package/plugins/litclaude/skills/skill-observer/scripts/skill-loop.mjs +0 -10
@@ -22,32 +22,13 @@ export const canonicalSkillResourceManifest = Object.freeze(
22
22
  ["lib/rules/matcher.mjs", "83334358a035a9fedf3d7970b2454825d4ca8d0082b5a8779efc8ad8fdeecd5b"],
23
23
  ["lib/rules/ordering.mjs", "c6da1ef0b228b8f0932b54422d2bc9744eb6bfbb8f5bb70b30b8f1a798765d1c"],
24
24
  ["lib/rules/scanner.mjs", "c62d672a66af25f29fe1d05b09288583425a2bf55f20d788f81d1578035f79f9"],
25
- ["lib/rules/session-state.mjs", "8f700f2fb0debd03c7141b93db980412db32d61524deef63144c12f9f5063d85"],
25
+ ["lib/rules/session-state.mjs", "ad660cadf147b102437da95a771bae19ee2140bf39c58915b8cb498e8d04f2af"],
26
26
  ["lib/secret-shapes.mjs", "73733bb5927d6d1e00c529acc847b79d8664d10f94100bcc7452824615ab87ba"],
27
27
  ["lib/secure-path-read.mjs", "c2991d0279f7399c3022c78babe28e653260f2a2e9ff4a5df692883ccf8807fd"],
28
- ["lib/skill-loop/apply.mjs", "c00aa7a40d76cb5390ac7c13af4a2eda4ff8d090f10c2a4f1e4a72ac158d3ac9"],
29
- ["lib/skill-loop/authority.mjs", "41911124a5141480538bbd9887c4d5cec2ea38552ed03c05bd3d3412f0c11b1d"],
30
- ["lib/skill-loop/cli.mjs", "333135a39c36e7eab6b10d5a3859dce68ba98b7c83bbf36144d53b19f4bdc1f4"],
31
- ["lib/skill-loop/common.mjs", "c65eec46eaad369b989337876960dcba9a0f25f40320862dc1c4a298a7e7a730"],
32
- ["lib/skill-loop/config.mjs", "833e73f74ffdf43068a95aa6c50dc29e9080b0549871bdc0a5669a01c752cba6"],
33
- ["lib/skill-loop/curator.mjs", "6776eb46fb0c1f79a2a217f495b2195e54cac044f61dfe9298447c9b2bd2de76"],
34
- ["lib/skill-loop/hook-runtime.mjs", "34af32eb44e04aed2f452dcc01556f4f113b9e4896bcdb633402c4e273763006"],
35
- ["lib/skill-loop/hook-state.mjs", "cbe544247069ee004acb389f70ef7d9f25f0afd8a5df9c5567c365bf47989938"],
36
- ["lib/skill-loop/ledger.mjs", "5367b62c21f8bad2e8efb34caa9e2a5c79de85d9d762d7a583cc66ea9da325e5"],
37
- ["lib/skill-loop/mutation-transaction.mjs", "485b871b3cc5e8c3234df9bf0a84f240fddc4f91dbff94607d1e046248bf33f9"],
38
- ["lib/skill-loop/proposal-reader.mjs", "b979d2a848eef091fabb001ce2e8a8a4fd8dc6e16e874add0f34dc99b3575af4"],
39
- ["lib/skill-loop/proposal-record.mjs", "352888ab9d0b7235414ca9ce068c119fb2230d1abe46bcff602d55feff0552bc"],
40
- ["lib/skill-loop/proposal-store.mjs", "07e06e035a8fd5b58baf41fd6df244f120bb14a857ea6b98f0d71f362dd4c358"],
41
- ["lib/skill-loop/proposals.mjs", "bc0a880b149c8293065d458eaf464ba79e08da93b1912c695239af8cbb8cedec"],
42
- ["lib/skill-loop/review-runner.mjs", "fcd4f1c122b3c50934358c376cda544808c0286793e1e882fee72277258d4c7c"],
43
- ["lib/skill-loop/review-transaction.mjs", "a74f21f6d24014d1ade5c6bf760d7452ed491df4471f7d14a8ef3423028db4c3"],
44
- ["lib/skill-loop/review.mjs", "90118466b694bc56aa605e4eeb1fbd34ce57367c4075adbb52d2ec2f2182bc45"],
45
- ["lib/skill-loop/usage.mjs", "9a80b2979014c46bdd9527140ef193abacd3a1b4eafd458f32315d5902aa2b99"],
46
- ["lib/skill-observer.mjs", "13466c511a870cb4809e327c8a9181cdba17da9883ef05995f6ca8c698a925b6"],
47
28
  ["lib/strict-json.mjs", "d941f327aabce5ffc212770852cfbc7e08dd03471e4e9bc9560eff54f06d489a"],
48
- ["lib/wikify-knowledge-cli.mjs", "d98e4b3ea0efe3dcd483750c2dc3a5d0dfd14bc6eb349437902b1e01b8fa2ff4"],
49
- ["lib/wikify-knowledge.mjs", "4d2f4e0ac3bc0b40ac513470b324e853e2aeeb3f009216c13d8e12ec08e6c28b"],
50
- ["scripts/scaffold-plan.mjs", "c014b9acb49a647c97044dfb5a83eb5cc2bd4e13774ad1a9f213b9e074559d3e"],
29
+ ["lib/wikify-knowledge-cli.mjs", "6835f8d6d98a3dd26266409366c23b2da437c0cd41e63537f0f200667655132b"],
30
+ ["lib/wikify-knowledge.mjs", "00dc8509d007484105dab87c1562d1657ef0f01ac8840a7d2f18898768d375b3"],
31
+ ["scripts/scaffold-plan.mjs", "b171ffccdaff8b9a586fe65590a154eeb663e7359fd34b6ee0e942d452381d2a"],
51
32
  ["skills/autoconference/SKILL.md", "3731208dc3838d4d2916fb01da0c8cb63cca36342c2ffb5770770b3332d06cd3"],
52
33
  ["skills/autoresearch/SKILL.md", "2b69c53bade3851cfb9c770bce80afe5f9e75b9629719e9ba95a4b0045eaf5b3"],
53
34
  ["skills/browser-drive/scripts/capability-probe.mjs", "e767912bc034eaf9081256d8aa2f2bf63c7d9d29e39cb5e56f390e872df794f7"],
@@ -187,12 +168,9 @@ export const canonicalSkillResourceManifest = Object.freeze(
187
168
  ["skills/lit-comprehend/references/micro-worlds.md", "92fc1401fc14c5bcbbeded00ff45819aacd2ead50b86f8a0f45985d19461bfa4"],
188
169
  ["skills/lit-comprehend/scripts/verify-explainer.mjs", "837a6fe18e2fea8ed015c8e024b36f9f7e2ebb7f6abe433fc69b1db60a152994"],
189
170
  ["skills/lit-team/SKILL.md", "6d4ca726d1240db7692ccc95802ab6abfd45588147ec663dafa1b3b233a08a13"],
190
- ["skills/lit-team/scripts/team.mjs", "ef259aa156d8476a40d0ac6f6f3ac54b81bde35c88bf3a0467878ffd57714931"],
171
+ ["skills/lit-team/scripts/team.mjs", "a0be61f09cc1c10989b12709135ac4b99260496f840dcf58514478082e239fe7"],
191
172
  ["skills/litresearch/ATTRIBUTION.md", "1ed8b43d48a13d7fc106cbfd86bc1bac0316529bdac4e2e38043fafeae2a8e15"],
192
173
  ["skills/litwork/SKILL.md", "18d91a20d882d72f9795cfeb8d46a3e07c773d66853d51aa965cdcec5513bea3"],
193
- ["skills/skill-observer/SKILL.md", "a67b72d877c45f519711f59e285302e1c1c8afbdda85009df07f1cf30a1a5d57"],
194
- ["skills/skill-observer/references/review-contract.md", "f55dc8334f3b8d8498c84d57725fa03af8fbd689b2dd7cce25e49931629ee54f"],
195
- ["skills/skill-observer/scripts/skill-loop.mjs", "36915455a8f22a03975108d48dea83e8f6aa827936f9fdac3c09eca3fec9072d"],
196
174
  ["skills/visual-qa/SKILL.md", "089d26a14800b461cfd628b4789f676308d5e09fe13e9e24632b8fd6305c8517"],
197
175
  ["skills/visual-qa/references/capture-playbook.md", "92cad16893118c9e878cabbb49dc2187049ec6e759fcc4e6006d79bdae998689"],
198
176
  ["skills/visual-qa/references/complete-contract.md", "8dd878975eb386b8b5d0b5439ed7bc50de9c4e3ee2aeff6d553c4299c5b30cb4"],
@@ -6,6 +6,7 @@
6
6
  import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
7
7
  import { join, resolve } from "node:path";
8
8
 
9
+ import { resolveProjectStateRoot } from "./project-state-root.mjs";
9
10
  import { findProjectRoot } from "./rules/discovery.mjs";
10
11
 
11
12
  export const LIT_PLAN_MAX_BLOCKS = 2;
@@ -15,7 +16,7 @@ export const LIT_PLAN_BLOCK_REASON = "lit-plan must persist plans/<slug>.md (run
15
16
  const MTIME_TOLERANCE_MS = 1000;
16
17
  const TASK_ROW = /^- \[ \] \d+\./mu;
17
18
 
18
- export const litPlanTurnPath = (cwd = process.cwd()) => join(resolve(cwd), ".litclaude", "lit-plan", "turn.json");
19
+ export const litPlanTurnPath = (cwd = resolveProjectStateRoot()) => join(resolveProjectStateRoot(cwd), ".litclaude", "lit-plan", "turn.json");
19
20
 
20
21
  const readTurn = (cwd) => {
21
22
  const path = litPlanTurnPath(cwd);
@@ -1,12 +1,14 @@
1
- import { join, resolve } from "node:path";
1
+ import { join } from "node:path";
2
2
 
3
- export const litgoalStateDir = (cwd = process.cwd()) => join(resolve(cwd), ".litclaude", "litgoal");
4
- export const litgoalBriefPath = (cwd = process.cwd()) => join(litgoalStateDir(cwd), "brief.md");
5
- export const litgoalGoalsPath = (cwd = process.cwd()) => join(litgoalStateDir(cwd), "goals.json");
6
- export const litgoalLedgerPath = (cwd = process.cwd()) => join(litgoalStateDir(cwd), "ledger.jsonl");
7
- export const litgoalLockDir = (cwd = process.cwd()) => join(litgoalStateDir(cwd), ".lock");
8
- export const litgoalSessionsDir = (cwd = process.cwd()) => join(litgoalStateDir(cwd), "sessions");
3
+ import { resolveProjectStateRoot } from "../project-state-root.mjs";
4
+
5
+ export const litgoalStateDir = (cwd = resolveProjectStateRoot()) => join(resolveProjectStateRoot(cwd), ".litclaude", "litgoal");
6
+ export const litgoalBriefPath = (cwd = resolveProjectStateRoot()) => join(litgoalStateDir(cwd), "brief.md");
7
+ export const litgoalGoalsPath = (cwd = resolveProjectStateRoot()) => join(litgoalStateDir(cwd), "goals.json");
8
+ export const litgoalLedgerPath = (cwd = resolveProjectStateRoot()) => join(litgoalStateDir(cwd), "ledger.jsonl");
9
+ export const litgoalLockDir = (cwd = resolveProjectStateRoot()) => join(litgoalStateDir(cwd), ".lock");
10
+ export const litgoalSessionsDir = (cwd = resolveProjectStateRoot()) => join(litgoalStateDir(cwd), "sessions");
9
11
  // Where a completed goal is preserved when a fresh session opens new state (drift 6a).
10
12
  // The session id is filename-sanitized because it arrives from the host.
11
- export const litgoalSessionArchivePath = (cwd = process.cwd(), sessionId = "unknown") =>
13
+ export const litgoalSessionArchivePath = (cwd = resolveProjectStateRoot(), sessionId = "unknown") =>
12
14
  join(litgoalSessionsDir(cwd), `${String(sessionId).replace(/[^A-Za-z0-9_-]/gu, "").slice(0, 64) || "unknown"}.json`);
@@ -0,0 +1,22 @@
1
+ import { existsSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
+
4
+ import { findProjectRoot } from "./rules/discovery.mjs";
5
+
6
+ const outermostGitRoot = (startDir) => {
7
+ let root = null;
8
+ let current = startDir;
9
+ for (;;) {
10
+ if (existsSync(join(current, ".git"))) root = current;
11
+ const parent = dirname(current);
12
+ if (parent === current) return root;
13
+ current = parent;
14
+ }
15
+ };
16
+
17
+ // State placement is intentionally separate from nearest-marker rule discovery: a nested
18
+ // package.json is a valid rules boundary, but .litclaude belongs at the outer Git root.
19
+ export const resolveProjectStateRoot = (startDir = process.cwd()) => {
20
+ const start = resolve(startDir);
21
+ return outermostGitRoot(start) ?? findProjectRoot(start) ?? start;
22
+ };
@@ -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);
@@ -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`);
@@ -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
+ }