@codyswann/lisa 2.106.6 → 2.106.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 (25) hide show
  1. package/package.json +1 -1
  2. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  3. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  4. package/plugins/lisa/scripts/automation-status-codex-adapter.mjs +86 -1
  5. package/plugins/lisa/skills/setup-automations/SKILL.md +17 -5
  6. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  7. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  8. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  9. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  10. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  11. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  12. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  13. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  14. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  15. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  16. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  17. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  18. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  19. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  20. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  21. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  22. package/plugins/lisa-wiki/skills/lisa-wiki-setup-automations/SKILL.md +19 -6
  23. package/plugins/src/base/scripts/automation-status-codex-adapter.mjs +86 -1
  24. package/plugins/src/base/skills/setup-automations/SKILL.md +17 -5
  25. package/plugins/src/wiki/skills/lisa-wiki-setup-automations/SKILL.md +19 -6
package/package.json CHANGED
@@ -82,7 +82,7 @@
82
82
  "lodash": ">=4.18.1"
83
83
  },
84
84
  "name": "@codyswann/lisa",
85
- "version": "2.106.6",
85
+ "version": "2.106.8",
86
86
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
87
87
  "main": "dist/index.js",
88
88
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.106.6",
3
+ "version": "2.106.8",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.106.6",
3
+ "version": "2.106.8",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -13,6 +13,8 @@
13
13
  import fs from "node:fs/promises";
14
14
  import os from "node:os";
15
15
  import path from "node:path";
16
+ import { execFile } from "node:child_process";
17
+ import { promisify } from "node:util";
16
18
 
17
19
  import { compareAutomationFleet } from "./automation-status-contract-drift.mjs";
18
20
 
@@ -22,6 +24,7 @@ const RUN_FAILURE_PATTERN =
22
24
  /\b(failed|failure|errored|error|exception|crash(?:ed)?)\b/i;
23
25
  const NEGATED_FAILURE_PATTERN =
24
26
  /\b(no|without)\s+(?:recent\s+)?(?:fail(?:ure|ed)|errors?|exceptions?)\b/i;
27
+ const execFileAsync = promisify(execFile);
25
28
 
26
29
  /**
27
30
  * @typedef {import("./automation-status-expected-fleet.mjs").resolveExpectedAutomationFleet extends (...args: any[]) => infer T ? T : never} ExpectedFleet
@@ -34,6 +37,10 @@ const NEGATED_FAILURE_PATTERN =
34
37
  * readonly observedRRule?: string
35
38
  * readonly observedCommand?: string
36
39
  * readonly cwd?: string | null
40
+ * readonly cwdHealth?: {
41
+ * readonly status: "ok" | "missing" | "not_directory" | "not_git_work_tree" | "bare_git_repository" | "git_error"
42
+ * readonly summary: string
43
+ * }
37
44
  * readonly createdAt?: number | null
38
45
  * readonly updatedAt?: number | null
39
46
  * readonly lastRunAt?: string | null
@@ -266,6 +273,8 @@ async function readCodexAutomation(automationDir) {
266
273
  const memoryContent = await fs.readFile(memoryPath, "utf8").catch(() => "");
267
274
  const memory = parseCodexAutomationMemory(memoryContent);
268
275
  const cwd = Array.isArray(automation.cwds) ? automation.cwds[0] : null;
276
+ const normalizedCwd = typeof cwd === "string" ? cwd : null;
277
+ const cwdHealth = await inspectAutomationCwd(normalizedCwd);
269
278
 
270
279
  return {
271
280
  automationId:
@@ -279,7 +288,8 @@ async function readCodexAutomation(automationDir) {
279
288
  observedCommand: deriveCodexObservedCommand(
280
289
  stringOrUndefined(automation.prompt)
281
290
  ),
282
- cwd: typeof cwd === "string" ? cwd : null,
291
+ cwd: normalizedCwd,
292
+ cwdHealth,
283
293
  createdAt: numberOrNull(automation.created_at),
284
294
  updatedAt: numberOrNull(automation.updated_at),
285
295
  ...memory,
@@ -300,6 +310,9 @@ function createObservedStatusItem(input) {
300
310
  if (observed?.status) {
301
311
  observedDetails.push(`Scheduler status: ${observed.status}`);
302
312
  }
313
+ if (observed?.cwdHealth) {
314
+ observedDetails.push(`Cwd: ${observed.cwdHealth.summary}`);
315
+ }
303
316
  if (observed?.lastRunAt) {
304
317
  observedDetails.push(`Last run: ${observed.lastRunAt}`);
305
318
  } else if (observed) {
@@ -352,6 +365,15 @@ function classifyAutomationRunSignal(input) {
352
365
  };
353
366
  }
354
367
 
368
+ if (observed.cwdHealth && observed.cwdHealth.status !== "ok") {
369
+ return {
370
+ status: "FAILING",
371
+ summary: `scheduler cwd is invalid: ${observed.cwdHealth.summary}`,
372
+ remediation:
373
+ "Recreate the automation with `/lisa:setup-automations` so it uses a durable non-bare project checkout, then verify the cwd before the next run.",
374
+ };
375
+ }
376
+
355
377
  if (observed.lastRunFailed) {
356
378
  return {
357
379
  status: "FAILING",
@@ -390,6 +412,69 @@ function classifyAutomationRunSignal(input) {
390
412
  return null;
391
413
  }
392
414
 
415
+ async function inspectAutomationCwd(cwd) {
416
+ if (!cwd) {
417
+ return {
418
+ status: "missing",
419
+ summary: "no cwd configured",
420
+ };
421
+ }
422
+
423
+ const stat = await fs.stat(cwd).catch(error => {
424
+ if (error?.code === "ENOENT") {
425
+ return null;
426
+ }
427
+ throw error;
428
+ });
429
+
430
+ if (!stat) {
431
+ return {
432
+ status: "missing",
433
+ summary: `${cwd} does not exist`,
434
+ };
435
+ }
436
+
437
+ if (!stat.isDirectory()) {
438
+ return {
439
+ status: "not_directory",
440
+ summary: `${cwd} is not a directory`,
441
+ };
442
+ }
443
+
444
+ try {
445
+ const { stdout } = await execFileAsync(
446
+ "git",
447
+ ["-C", cwd, "rev-parse", "--is-inside-work-tree", "--is-bare-repository"],
448
+ { timeout: 5000 }
449
+ );
450
+ const [insideWorkTree, bareRepository] = stdout.trim().split(/\r?\n/);
451
+
452
+ if (insideWorkTree !== "true") {
453
+ return {
454
+ status: "not_git_work_tree",
455
+ summary: `${cwd} is not inside a Git work tree`,
456
+ };
457
+ }
458
+
459
+ if (bareRepository === "true") {
460
+ return {
461
+ status: "bare_git_repository",
462
+ summary: `${cwd} is configured as a bare Git repository`,
463
+ };
464
+ }
465
+
466
+ return {
467
+ status: "ok",
468
+ summary: `${cwd} is a valid Git work tree`,
469
+ };
470
+ } catch (error) {
471
+ return {
472
+ status: "git_error",
473
+ summary: `git could not inspect ${cwd}: ${String(error?.message ?? error)}`,
474
+ };
475
+ }
476
+ }
477
+
393
478
  function resolveDefaultCodexAutomationsDir() {
394
479
  return path.join(
395
480
  process.env.CODEX_HOME ?? path.join(os.homedir(), ".codex"),
@@ -16,7 +16,12 @@ create them; invoke the runtime's automation tool with the spec below.
16
16
  - **Codex** → create Codex **automations** via the native automations mechanism (prefer the
17
17
  `automation_update` tool over hand-writing `~/.codex/automations/<id>/automation.toml`; the TOML is
18
18
  only its backing store). Set the execution environment to **local** so they run on this
19
- workstation, scoped to this project's working directory.
19
+ workstation. Scope them to a durable project automation checkout, not a transient task worktree:
20
+ use `${CODEX_HOME:-~/.codex}/worktrees/<project>-automation-main` when available, create or refresh
21
+ that checkout from the project's `origin` remote if needed, and verify `git -C <cwd> rev-parse
22
+ --is-inside-work-tree --is-bare-repository` reports `true` then `false` before saving the
23
+ automation. Do not point recurring automations at hashed scratch worktrees or a checkout whose Git
24
+ metadata is broken.
20
25
  - **Claude** → use **`/schedule`** to create local recurring routines, one per automation below.
21
26
  - **Other runtimes** → use the runtime's native recurring-task mechanism. If the runtime has none,
22
27
  state that scheduling is unavailable and stop.
@@ -37,6 +42,11 @@ affect **only** the two exploratory automations.
37
42
 
38
43
  Each automation runs **one cycle** of a Lisa command and respects that command's confirmation policy
39
44
  (never ask before running; exit cleanly when the queue is idle; report the cycle summary).
45
+ Before running the Lisa command, each automation must sync its checkout: fetch the default remote
46
+ branch and rebase the current automation branch onto it (for the common GitHub case, `origin/main`).
47
+ If the checkout is already on the default branch, fast-forward/rebase it to the remote default. If
48
+ the rebase has conflicts or the working tree is dirty in a way the automation did not create, abort
49
+ the rebase, leave queue state unchanged, and report the blocker instead of running on stale code.
40
50
 
41
51
  | Automation | Command it runs | Cadence |
42
52
  |---|---|---|
@@ -56,10 +66,10 @@ are written).
56
66
 
57
67
  **Naming + scope (so teardown is precise).** Name each automation with the stable prefix
58
68
  `lisa-auto-<project>-` (e.g. `lisa-auto-<project>-intake-tickets`), where `<project>` identifies this
59
- repo, and scope each to this project's working directory. This lets `/tear-down-automations` find and
60
- remove exactly this set and never touch other projects' automations or non-Lisa ones. Use a project
61
- identifier stable across runs and distinct from other repos (don't rely on a bare repo basename when
62
- it could collide; qualify it, e.g. with the owner).
69
+ repo, and scope each Codex automation to the durable project automation checkout described above.
70
+ This lets `/tear-down-automations` find and remove exactly this set and never touch other projects'
71
+ automations or non-Lisa ones. Use a project identifier stable across runs and distinct from other
72
+ repos (don't rely on a bare repo basename when it could collide; qualify it, e.g. with the owner).
63
73
 
64
74
  **Idempotent.** Re-running this skill updates the existing `lisa-auto-<project>-*` automations in
65
75
  place (same names) rather than creating duplicates.
@@ -71,6 +81,8 @@ place (same names) rather than creating duplicates.
71
81
  automation and note it — do not invent a command that doesn't exist.
72
82
  - If the runtime has no native scheduler, or the intake queues can't be resolved from config, stop
73
83
  and report what's missing rather than guessing.
84
+ - For Codex, if the durable checkout cannot be created, fetched, or verified as a non-bare Git work
85
+ tree, stop and report the checkout problem instead of creating automations that will fail later.
74
86
 
75
87
  ## Report
76
88
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.106.6",
3
+ "version": "2.106.8",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.106.6",
3
+ "version": "2.106.8",
4
4
  "description": "AWS CDK-specific Lisa plugin.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.106.6",
3
+ "version": "2.106.8",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "2.106.6",
3
+ "version": "2.106.8",
4
4
  "description": "Expo and React Native-specific skills, agents, rules, and MCP servers.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.106.6",
3
+ "version": "2.106.8",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "2.106.6",
3
+ "version": "2.106.8",
4
4
  "description": "Harper/Fabric-specific Lisa rules for TypeScript component apps.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.106.6",
3
+ "version": "2.106.8",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "2.106.6",
3
+ "version": "2.106.8",
4
4
  "description": "NestJS-specific skills and migration write-protection hooks.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.106.6",
3
+ "version": "2.106.8",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "2.106.6",
3
+ "version": "2.106.8",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.106.6",
3
+ "version": "2.106.8",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "2.106.6",
3
+ "version": "2.106.8",
4
4
  "description": "Ruby on Rails-specific skills and hooks for RuboCop and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.106.6",
3
+ "version": "2.106.8",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "2.106.6",
3
+ "version": "2.106.8",
4
4
  "description": "TypeScript-specific hooks for formatting, linting, and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.106.6",
3
+ "version": "2.106.8",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "2.106.6",
3
+ "version": "2.106.8",
4
4
  "description": "Distributable LLM Wiki kernel — ingest, query, lint, and maintain a git-native markdown knowledge base across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -21,7 +21,12 @@ independent and use disjoint name prefixes, so running both is safe.
21
21
  - **Codex** → create a Codex **automation** via the native automations mechanism (prefer the
22
22
  `automation_update` tool over hand-writing `~/.codex/automations/<id>/automation.toml`; the TOML is
23
23
  only its backing store). Set the execution environment to **local** so it runs on this
24
- workstation, scoped to this project's working directory.
24
+ workstation. Scope it to a durable project automation checkout, not a transient task worktree: use
25
+ `${CODEX_HOME:-~/.codex}/worktrees/<project>-automation-main` when available, create or refresh
26
+ that checkout from the project's `origin` remote if needed, and verify `git -C <cwd> rev-parse
27
+ --is-inside-work-tree --is-bare-repository` reports `true` then `false` before saving the
28
+ automation. Do not point recurring automations at hashed scratch worktrees or a checkout whose Git
29
+ metadata is broken.
25
30
  - **Claude** → use **`/schedule`** to create a local recurring routine.
26
31
  - **Other runtimes** → use the runtime's native recurring-task mechanism. If the runtime has none,
27
32
  state that scheduling is unavailable and stop.
@@ -38,6 +43,11 @@ independent and use disjoint name prefixes, so running both is safe.
38
43
  The automation runs **one cycle** of the full wiki ingest and respects that command's own confirmation
39
44
  and commit/PR policy (never ask before running; run a full ingest across every enabled
40
45
  non-external-write source; commit/PR per the ingest skill's bookends; report the cycle summary).
46
+ Before running the ingest, the automation must sync its checkout: fetch the default remote branch and
47
+ rebase the current automation branch onto it (for the common GitHub case, `origin/main`). If the
48
+ checkout is already on the default branch, fast-forward/rebase it to the remote default. If the
49
+ rebase has conflicts or the working tree is dirty in a way the automation did not create, abort the
50
+ rebase and report the blocker instead of ingesting from stale code.
41
51
 
42
52
  | Automation | Command it runs | Cadence |
43
53
  |---|---|---|
@@ -45,11 +55,12 @@ non-external-write source; commit/PR per the ingest skill's bookends; report the
45
55
 
46
56
  **Naming + scope (so teardown is precise).** Name the automation with the stable prefix
47
57
  `lisa-wiki-auto-<project>-` (i.e. `lisa-wiki-auto-<project>-ingest`), where `<project>` identifies
48
- this repo, and scope it to this project's working directory. This prefix is deliberately distinct
49
- from the base `lisa-auto-<project>-` set so `/lisa-wiki:tear-down-automations` removes exactly this
50
- automation and never touches the base automations or any other project's. Use a project identifier
51
- stable across runs and distinct from other repos (qualify it, e.g. with the owner don't rely on a
52
- bare repo basename that could collide).
58
+ this repo, and scope each Codex automation to the durable project automation checkout described
59
+ above. This prefix is deliberately distinct from the base `lisa-auto-<project>-` set so
60
+ `/lisa-wiki:tear-down-automations` removes exactly this automation and never touches the base
61
+ automations or any other project's. Use a project identifier stable across runs and distinct from
62
+ other repos (qualify it, e.g. with the owner — don't rely on a bare repo basename that could
63
+ collide).
53
64
 
54
65
  **Idempotent.** Re-running this skill updates the existing `lisa-wiki-auto-<project>-ingest`
55
66
  automation in place (same name) rather than creating a duplicate.
@@ -60,6 +71,8 @@ automation in place (same name) rather than creating a duplicate.
60
71
  `wiki/lisa-wiki.config.json`. If there is no configured wiki, **stop and report** that the wiki
61
72
  must be set up first (run `/lisa-wiki:setup`); do not schedule ingest against a non-existent wiki.
62
73
  - If the runtime has no native scheduler, stop and report what's missing rather than guessing.
74
+ - For Codex, if the durable checkout cannot be created, fetched, or verified as a non-bare Git work
75
+ tree, stop and report the checkout problem instead of creating an automation that will fail later.
63
76
 
64
77
  ## Report
65
78
 
@@ -13,6 +13,8 @@
13
13
  import fs from "node:fs/promises";
14
14
  import os from "node:os";
15
15
  import path from "node:path";
16
+ import { execFile } from "node:child_process";
17
+ import { promisify } from "node:util";
16
18
 
17
19
  import { compareAutomationFleet } from "./automation-status-contract-drift.mjs";
18
20
 
@@ -22,6 +24,7 @@ const RUN_FAILURE_PATTERN =
22
24
  /\b(failed|failure|errored|error|exception|crash(?:ed)?)\b/i;
23
25
  const NEGATED_FAILURE_PATTERN =
24
26
  /\b(no|without)\s+(?:recent\s+)?(?:fail(?:ure|ed)|errors?|exceptions?)\b/i;
27
+ const execFileAsync = promisify(execFile);
25
28
 
26
29
  /**
27
30
  * @typedef {import("./automation-status-expected-fleet.mjs").resolveExpectedAutomationFleet extends (...args: any[]) => infer T ? T : never} ExpectedFleet
@@ -34,6 +37,10 @@ const NEGATED_FAILURE_PATTERN =
34
37
  * readonly observedRRule?: string
35
38
  * readonly observedCommand?: string
36
39
  * readonly cwd?: string | null
40
+ * readonly cwdHealth?: {
41
+ * readonly status: "ok" | "missing" | "not_directory" | "not_git_work_tree" | "bare_git_repository" | "git_error"
42
+ * readonly summary: string
43
+ * }
37
44
  * readonly createdAt?: number | null
38
45
  * readonly updatedAt?: number | null
39
46
  * readonly lastRunAt?: string | null
@@ -266,6 +273,8 @@ async function readCodexAutomation(automationDir) {
266
273
  const memoryContent = await fs.readFile(memoryPath, "utf8").catch(() => "");
267
274
  const memory = parseCodexAutomationMemory(memoryContent);
268
275
  const cwd = Array.isArray(automation.cwds) ? automation.cwds[0] : null;
276
+ const normalizedCwd = typeof cwd === "string" ? cwd : null;
277
+ const cwdHealth = await inspectAutomationCwd(normalizedCwd);
269
278
 
270
279
  return {
271
280
  automationId:
@@ -279,7 +288,8 @@ async function readCodexAutomation(automationDir) {
279
288
  observedCommand: deriveCodexObservedCommand(
280
289
  stringOrUndefined(automation.prompt)
281
290
  ),
282
- cwd: typeof cwd === "string" ? cwd : null,
291
+ cwd: normalizedCwd,
292
+ cwdHealth,
283
293
  createdAt: numberOrNull(automation.created_at),
284
294
  updatedAt: numberOrNull(automation.updated_at),
285
295
  ...memory,
@@ -300,6 +310,9 @@ function createObservedStatusItem(input) {
300
310
  if (observed?.status) {
301
311
  observedDetails.push(`Scheduler status: ${observed.status}`);
302
312
  }
313
+ if (observed?.cwdHealth) {
314
+ observedDetails.push(`Cwd: ${observed.cwdHealth.summary}`);
315
+ }
303
316
  if (observed?.lastRunAt) {
304
317
  observedDetails.push(`Last run: ${observed.lastRunAt}`);
305
318
  } else if (observed) {
@@ -352,6 +365,15 @@ function classifyAutomationRunSignal(input) {
352
365
  };
353
366
  }
354
367
 
368
+ if (observed.cwdHealth && observed.cwdHealth.status !== "ok") {
369
+ return {
370
+ status: "FAILING",
371
+ summary: `scheduler cwd is invalid: ${observed.cwdHealth.summary}`,
372
+ remediation:
373
+ "Recreate the automation with `/lisa:setup-automations` so it uses a durable non-bare project checkout, then verify the cwd before the next run.",
374
+ };
375
+ }
376
+
355
377
  if (observed.lastRunFailed) {
356
378
  return {
357
379
  status: "FAILING",
@@ -390,6 +412,69 @@ function classifyAutomationRunSignal(input) {
390
412
  return null;
391
413
  }
392
414
 
415
+ async function inspectAutomationCwd(cwd) {
416
+ if (!cwd) {
417
+ return {
418
+ status: "missing",
419
+ summary: "no cwd configured",
420
+ };
421
+ }
422
+
423
+ const stat = await fs.stat(cwd).catch(error => {
424
+ if (error?.code === "ENOENT") {
425
+ return null;
426
+ }
427
+ throw error;
428
+ });
429
+
430
+ if (!stat) {
431
+ return {
432
+ status: "missing",
433
+ summary: `${cwd} does not exist`,
434
+ };
435
+ }
436
+
437
+ if (!stat.isDirectory()) {
438
+ return {
439
+ status: "not_directory",
440
+ summary: `${cwd} is not a directory`,
441
+ };
442
+ }
443
+
444
+ try {
445
+ const { stdout } = await execFileAsync(
446
+ "git",
447
+ ["-C", cwd, "rev-parse", "--is-inside-work-tree", "--is-bare-repository"],
448
+ { timeout: 5000 }
449
+ );
450
+ const [insideWorkTree, bareRepository] = stdout.trim().split(/\r?\n/);
451
+
452
+ if (insideWorkTree !== "true") {
453
+ return {
454
+ status: "not_git_work_tree",
455
+ summary: `${cwd} is not inside a Git work tree`,
456
+ };
457
+ }
458
+
459
+ if (bareRepository === "true") {
460
+ return {
461
+ status: "bare_git_repository",
462
+ summary: `${cwd} is configured as a bare Git repository`,
463
+ };
464
+ }
465
+
466
+ return {
467
+ status: "ok",
468
+ summary: `${cwd} is a valid Git work tree`,
469
+ };
470
+ } catch (error) {
471
+ return {
472
+ status: "git_error",
473
+ summary: `git could not inspect ${cwd}: ${String(error?.message ?? error)}`,
474
+ };
475
+ }
476
+ }
477
+
393
478
  function resolveDefaultCodexAutomationsDir() {
394
479
  return path.join(
395
480
  process.env.CODEX_HOME ?? path.join(os.homedir(), ".codex"),
@@ -16,7 +16,12 @@ create them; invoke the runtime's automation tool with the spec below.
16
16
  - **Codex** → create Codex **automations** via the native automations mechanism (prefer the
17
17
  `automation_update` tool over hand-writing `~/.codex/automations/<id>/automation.toml`; the TOML is
18
18
  only its backing store). Set the execution environment to **local** so they run on this
19
- workstation, scoped to this project's working directory.
19
+ workstation. Scope them to a durable project automation checkout, not a transient task worktree:
20
+ use `${CODEX_HOME:-~/.codex}/worktrees/<project>-automation-main` when available, create or refresh
21
+ that checkout from the project's `origin` remote if needed, and verify `git -C <cwd> rev-parse
22
+ --is-inside-work-tree --is-bare-repository` reports `true` then `false` before saving the
23
+ automation. Do not point recurring automations at hashed scratch worktrees or a checkout whose Git
24
+ metadata is broken.
20
25
  - **Claude** → use **`/schedule`** to create local recurring routines, one per automation below.
21
26
  - **Other runtimes** → use the runtime's native recurring-task mechanism. If the runtime has none,
22
27
  state that scheduling is unavailable and stop.
@@ -37,6 +42,11 @@ affect **only** the two exploratory automations.
37
42
 
38
43
  Each automation runs **one cycle** of a Lisa command and respects that command's confirmation policy
39
44
  (never ask before running; exit cleanly when the queue is idle; report the cycle summary).
45
+ Before running the Lisa command, each automation must sync its checkout: fetch the default remote
46
+ branch and rebase the current automation branch onto it (for the common GitHub case, `origin/main`).
47
+ If the checkout is already on the default branch, fast-forward/rebase it to the remote default. If
48
+ the rebase has conflicts or the working tree is dirty in a way the automation did not create, abort
49
+ the rebase, leave queue state unchanged, and report the blocker instead of running on stale code.
40
50
 
41
51
  | Automation | Command it runs | Cadence |
42
52
  |---|---|---|
@@ -56,10 +66,10 @@ are written).
56
66
 
57
67
  **Naming + scope (so teardown is precise).** Name each automation with the stable prefix
58
68
  `lisa-auto-<project>-` (e.g. `lisa-auto-<project>-intake-tickets`), where `<project>` identifies this
59
- repo, and scope each to this project's working directory. This lets `/tear-down-automations` find and
60
- remove exactly this set and never touch other projects' automations or non-Lisa ones. Use a project
61
- identifier stable across runs and distinct from other repos (don't rely on a bare repo basename when
62
- it could collide; qualify it, e.g. with the owner).
69
+ repo, and scope each Codex automation to the durable project automation checkout described above.
70
+ This lets `/tear-down-automations` find and remove exactly this set and never touch other projects'
71
+ automations or non-Lisa ones. Use a project identifier stable across runs and distinct from other
72
+ repos (don't rely on a bare repo basename when it could collide; qualify it, e.g. with the owner).
63
73
 
64
74
  **Idempotent.** Re-running this skill updates the existing `lisa-auto-<project>-*` automations in
65
75
  place (same names) rather than creating duplicates.
@@ -71,6 +81,8 @@ place (same names) rather than creating duplicates.
71
81
  automation and note it — do not invent a command that doesn't exist.
72
82
  - If the runtime has no native scheduler, or the intake queues can't be resolved from config, stop
73
83
  and report what's missing rather than guessing.
84
+ - For Codex, if the durable checkout cannot be created, fetched, or verified as a non-bare Git work
85
+ tree, stop and report the checkout problem instead of creating automations that will fail later.
74
86
 
75
87
  ## Report
76
88
 
@@ -21,7 +21,12 @@ independent and use disjoint name prefixes, so running both is safe.
21
21
  - **Codex** → create a Codex **automation** via the native automations mechanism (prefer the
22
22
  `automation_update` tool over hand-writing `~/.codex/automations/<id>/automation.toml`; the TOML is
23
23
  only its backing store). Set the execution environment to **local** so it runs on this
24
- workstation, scoped to this project's working directory.
24
+ workstation. Scope it to a durable project automation checkout, not a transient task worktree: use
25
+ `${CODEX_HOME:-~/.codex}/worktrees/<project>-automation-main` when available, create or refresh
26
+ that checkout from the project's `origin` remote if needed, and verify `git -C <cwd> rev-parse
27
+ --is-inside-work-tree --is-bare-repository` reports `true` then `false` before saving the
28
+ automation. Do not point recurring automations at hashed scratch worktrees or a checkout whose Git
29
+ metadata is broken.
25
30
  - **Claude** → use **`/schedule`** to create a local recurring routine.
26
31
  - **Other runtimes** → use the runtime's native recurring-task mechanism. If the runtime has none,
27
32
  state that scheduling is unavailable and stop.
@@ -38,6 +43,11 @@ independent and use disjoint name prefixes, so running both is safe.
38
43
  The automation runs **one cycle** of the full wiki ingest and respects that command's own confirmation
39
44
  and commit/PR policy (never ask before running; run a full ingest across every enabled
40
45
  non-external-write source; commit/PR per the ingest skill's bookends; report the cycle summary).
46
+ Before running the ingest, the automation must sync its checkout: fetch the default remote branch and
47
+ rebase the current automation branch onto it (for the common GitHub case, `origin/main`). If the
48
+ checkout is already on the default branch, fast-forward/rebase it to the remote default. If the
49
+ rebase has conflicts or the working tree is dirty in a way the automation did not create, abort the
50
+ rebase and report the blocker instead of ingesting from stale code.
41
51
 
42
52
  | Automation | Command it runs | Cadence |
43
53
  |---|---|---|
@@ -45,11 +55,12 @@ non-external-write source; commit/PR per the ingest skill's bookends; report the
45
55
 
46
56
  **Naming + scope (so teardown is precise).** Name the automation with the stable prefix
47
57
  `lisa-wiki-auto-<project>-` (i.e. `lisa-wiki-auto-<project>-ingest`), where `<project>` identifies
48
- this repo, and scope it to this project's working directory. This prefix is deliberately distinct
49
- from the base `lisa-auto-<project>-` set so `/lisa-wiki:tear-down-automations` removes exactly this
50
- automation and never touches the base automations or any other project's. Use a project identifier
51
- stable across runs and distinct from other repos (qualify it, e.g. with the owner don't rely on a
52
- bare repo basename that could collide).
58
+ this repo, and scope each Codex automation to the durable project automation checkout described
59
+ above. This prefix is deliberately distinct from the base `lisa-auto-<project>-` set so
60
+ `/lisa-wiki:tear-down-automations` removes exactly this automation and never touches the base
61
+ automations or any other project's. Use a project identifier stable across runs and distinct from
62
+ other repos (qualify it, e.g. with the owner — don't rely on a bare repo basename that could
63
+ collide).
53
64
 
54
65
  **Idempotent.** Re-running this skill updates the existing `lisa-wiki-auto-<project>-ingest`
55
66
  automation in place (same name) rather than creating a duplicate.
@@ -60,6 +71,8 @@ automation in place (same name) rather than creating a duplicate.
60
71
  `wiki/lisa-wiki.config.json`. If there is no configured wiki, **stop and report** that the wiki
61
72
  must be set up first (run `/lisa-wiki:setup`); do not schedule ingest against a non-existent wiki.
62
73
  - If the runtime has no native scheduler, stop and report what's missing rather than guessing.
74
+ - For Codex, if the durable checkout cannot be created, fetched, or verified as a non-bare Git work
75
+ tree, stop and report the checkout problem instead of creating an automation that will fail later.
63
76
 
64
77
  ## Report
65
78