@codyswann/lisa 4.27.4 → 4.27.6

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 (103) hide show
  1. package/all/copy-overwrite/scripts/lisa-enforcement-fallback.sh +27 -4
  2. package/all/copy-overwrite/scripts/lisa-work-item.mjs +167 -2
  3. package/dist/cli/doctor-enforcement-coverage.d.ts +14 -0
  4. package/dist/cli/doctor-enforcement-coverage.d.ts.map +1 -0
  5. package/dist/cli/doctor-enforcement-coverage.js +103 -0
  6. package/dist/cli/doctor-enforcement-coverage.js.map +1 -0
  7. package/dist/cli/doctor.d.ts.map +1 -1
  8. package/dist/cli/doctor.js +9 -0
  9. package/dist/cli/doctor.js.map +1 -1
  10. package/dist/core/enforcement-census-report.d.ts +19 -0
  11. package/dist/core/enforcement-census-report.d.ts.map +1 -0
  12. package/dist/core/enforcement-census-report.js +198 -0
  13. package/dist/core/enforcement-census-report.js.map +1 -0
  14. package/dist/core/enforcement-census.d.ts +177 -0
  15. package/dist/core/enforcement-census.d.ts.map +1 -0
  16. package/dist/core/enforcement-census.js +252 -0
  17. package/dist/core/enforcement-census.js.map +1 -0
  18. package/dist/core/enforcement-coverage.d.ts +168 -0
  19. package/dist/core/enforcement-coverage.d.ts.map +1 -0
  20. package/dist/core/enforcement-coverage.js +342 -0
  21. package/dist/core/enforcement-coverage.js.map +1 -0
  22. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  23. package/dist/core/lisa-owned-hash-ledger.js +10 -0
  24. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  25. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  26. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  27. package/dist/core/upstream-evidence-manifest.js +25 -7
  28. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  29. package/dist/migrations/ensure-push-default-safe.d.ts +50 -0
  30. package/dist/migrations/ensure-push-default-safe.d.ts.map +1 -0
  31. package/dist/migrations/ensure-push-default-safe.js +159 -0
  32. package/dist/migrations/ensure-push-default-safe.js.map +1 -0
  33. package/dist/migrations/index.d.ts +1 -0
  34. package/dist/migrations/index.d.ts.map +1 -1
  35. package/dist/migrations/index.js +3 -0
  36. package/dist/migrations/index.js.map +1 -1
  37. package/package.json +4 -3
  38. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  40. package/plugins/lisa/.codex-plugin/skills/lisa-setup-automations/SKILL.md +23 -0
  41. package/plugins/lisa/.codex-plugin/skills/lisa-sync-down/SKILL.md +8 -1
  42. package/plugins/lisa/skills/lisa-setup-automations/SKILL.md +23 -0
  43. package/plugins/lisa/skills/lisa-sync-down/SKILL.md +8 -1
  44. package/plugins/lisa-agy/plugin.json +1 -1
  45. package/plugins/lisa-agy/skills/lisa-setup-automations/SKILL.md +23 -0
  46. package/plugins/lisa-agy/skills/lisa-sync-down/SKILL.md +8 -1
  47. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  49. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  50. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-copilot/skills/lisa-setup-automations/SKILL.md +23 -0
  54. package/plugins/lisa-copilot/skills/lisa-sync-down/SKILL.md +8 -1
  55. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-cursor/skills/lisa-setup-automations/SKILL.md +23 -0
  57. package/plugins/lisa-cursor/skills/lisa-sync-down/SKILL.md +8 -1
  58. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  60. package/plugins/lisa-expo-agy/plugin.json +1 -1
  61. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  64. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  65. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  66. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  67. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  69. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  70. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  71. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  72. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  73. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  74. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  75. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  76. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  77. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  78. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  79. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  80. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  81. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  82. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  83. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  84. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  85. package/plugins/lisa-rails-agy/plugin.json +1 -1
  86. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  87. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  88. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  89. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  90. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  91. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  92. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  93. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  94. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  95. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  96. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  97. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  98. package/plugins/src/base/skills/lisa-setup-automations/SKILL.md +23 -0
  99. package/plugins/src/base/skills/lisa-sync-down/SKILL.md +8 -1
  100. package/scripts/lisa-commit-and-pr-local.sh +22 -6
  101. package/scripts/lisa-enforcement-census.mjs +168 -0
  102. package/scripts/lisa-enforcement-fallback.sh +27 -4
  103. package/typescript/copy-contents/.husky/pre-push +70 -0
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Fleet enforcement census — re-measures how many host checkouts resolve a
4
+ * Lisa enforcement guard, and reports it.
5
+ *
6
+ * The per-session staleness notice in `scripts/lisa-enforcement-fallback.sh`
7
+ * tells THIS session about THIS checkout, which is right and is not the gap.
8
+ * The gap is that nobody was looking at the fleet, so the only fleet-wide
9
+ * number that ever existed was taken by hand and frozen in a comment
10
+ * (CodySwannGT/lisa#3490). This runs the measurement again, every time.
11
+ *
12
+ * Two properties it must keep:
13
+ *
14
+ * - It REPORTS. Its exit status is 0 for every finding about the fleet,
15
+ * including a fleet where every checkout is stale. A census that could
16
+ * redden someone's build would be routed around.
17
+ * - It keeps "resolves nothing" apart from "resolves something old". They are
18
+ * different failures with different remedies, and folding them loses the
19
+ * serious half.
20
+ *
21
+ * Usage:
22
+ * node scripts/lisa-enforcement-census.mjs [options]
23
+ *
24
+ * Options:
25
+ * --roster <path> Roster file (default: .lisa.workspaces.json beside this repo)
26
+ * --scan <dir> Also discover checkouts under <dir>. Repeatable.
27
+ * --depth <n> How deep --scan descends (default 2)
28
+ * --reference <ver> Compare against this version instead of the newest found
29
+ * --redact Replace real paths with stable anonymous labels
30
+ * --json Emit the measured fleet as JSON
31
+ *
32
+ * Exit status: 0 for every fleet finding. 1 only when an explicitly named
33
+ * roster cannot be read, and 2 for a usage error — both are failures of the
34
+ * census's own inputs, never findings about the fleet.
35
+ */
36
+ import path from "node:path";
37
+ import { fileURLToPath } from "node:url";
38
+
39
+ const here = path.dirname(fileURLToPath(import.meta.url));
40
+ const repoRoot = path.resolve(here, "..");
41
+
42
+ const census = await import(
43
+ path.join(repoRoot, "dist", "core", "enforcement-census.js")
44
+ ).catch(error => {
45
+ process.stderr.write(
46
+ `Could not load the census from dist/. Run \`bun run build:dist\` first.\n${error.message}\n`
47
+ );
48
+ process.exit(2);
49
+ });
50
+ const report = await import(
51
+ path.join(repoRoot, "dist", "core", "enforcement-census-report.js")
52
+ );
53
+
54
+ const options = {
55
+ roster: null,
56
+ scans: [],
57
+ depth: 2,
58
+ reference: null,
59
+ redact: false,
60
+ json: false,
61
+ };
62
+
63
+ const argv = process.argv.slice(2);
64
+ for (let index = 0; index < argv.length; index += 1) {
65
+ const flag = argv[index];
66
+ const value = argv[index + 1];
67
+ // A value-taking flag left at the end of the line has no value. Assigning
68
+ // `undefined` there silently falls back to the default roster or drops the
69
+ // requested setting, so the run measures something other than what was
70
+ // asked for and says nothing about it — a census reporting the wrong fleet
71
+ // is worse than one that refuses. This is the census's own input being
72
+ // wrong, so it exits 2 like any other usage error and never as a finding.
73
+ const VALUE_FLAGS = ["--roster", "--scan", "--depth", "--reference"];
74
+ if (VALUE_FLAGS.includes(flag) && value === undefined) {
75
+ process.stderr.write(`${flag} requires a value\n`);
76
+ process.exit(2);
77
+ }
78
+ if (flag === "--roster") {
79
+ options.roster = value;
80
+ index += 1;
81
+ } else if (flag === "--scan") {
82
+ options.scans.push(value);
83
+ index += 1;
84
+ } else if (flag === "--depth") {
85
+ options.depth = Number.parseInt(value ?? "", 10);
86
+ index += 1;
87
+ } else if (flag === "--reference") {
88
+ options.reference = value;
89
+ index += 1;
90
+ } else if (flag === "--redact") {
91
+ options.redact = true;
92
+ } else if (flag === "--json") {
93
+ options.json = true;
94
+ } else if (flag === "--help" || flag === "-h") {
95
+ process.stdout.write(
96
+ "Usage: node scripts/lisa-enforcement-census.mjs [--roster <path>] [--scan <dir>] [--depth <n>] [--reference <ver>] [--redact] [--json]\n"
97
+ );
98
+ process.exit(0);
99
+ } else {
100
+ process.stderr.write(`Unknown option: ${flag}\n`);
101
+ process.exit(2);
102
+ }
103
+ }
104
+
105
+ if (!Number.isInteger(options.depth) || options.depth < 0) {
106
+ process.stderr.write("--depth must be a non-negative integer\n");
107
+ process.exit(2);
108
+ }
109
+
110
+ const rosterPath =
111
+ options.roster ?? path.join(repoRoot, census.DEFAULT_ROSTER_FILE);
112
+ const rosterEntries = await census.readFleetRoster(rosterPath);
113
+
114
+ // An explicitly named roster that cannot be read is an input error, not a
115
+ // finding: the operator asked for a specific file and did not get it. A missing
116
+ // DEFAULT roster is neither — this repository is simply not a fleet hub — so it
117
+ // reports an empty fleet and still exits 0.
118
+ if (rosterEntries === null && options.roster !== null) {
119
+ process.stderr.write(`Could not read the roster: ${rosterPath}\n`);
120
+ process.exit(1);
121
+ }
122
+
123
+ const scanned = (
124
+ await Promise.all(
125
+ options.scans.map(directory =>
126
+ census.scanForCheckouts(directory, options.depth)
127
+ )
128
+ )
129
+ ).flat();
130
+
131
+ const roster = await census.dedupeRoster([
132
+ ...(rosterEntries ?? []),
133
+ ...scanned,
134
+ ]);
135
+
136
+ const origins = [
137
+ rosterEntries === null ? null : rosterPath,
138
+ ...options.scans.map(directory => `scan of ${directory}`),
139
+ ].filter(Boolean);
140
+
141
+ const seedVersion = await import(path.join(repoRoot, "package.json"), {
142
+ with: { type: "json" },
143
+ })
144
+ .then(module => module.default.version ?? null)
145
+ .catch(() => null);
146
+
147
+ const measured = await census.runFleetCensus({
148
+ roster,
149
+ rosterOrigin: origins.length === 0 ? "no roster found" : origins.join(" + "),
150
+ seedVersion,
151
+ reference: options.reference,
152
+ });
153
+
154
+ const output = options.redact ? report.redactCensus(measured) : measured;
155
+
156
+ process.stdout.write(
157
+ options.json
158
+ ? `${JSON.stringify(output, null, 2)}\n`
159
+ : report.renderFleetCensusReport(output)
160
+ );
161
+
162
+ // Unconditional. Nothing above may change it.
163
+ //
164
+ // Set rather than called: `process.exit()` tears the process down synchronously
165
+ // and can drop a pending `process.stdout.write` when stdout is a pipe, which is
166
+ // exactly how this is read — piped to a pager, a file, or a scheduled loop's
167
+ // log. A truncated census reports a smaller fleet than it measured, silently.
168
+ process.exitCode = 0;
@@ -404,10 +404,33 @@ fi
404
404
  # - `main` cut 80 releases in 24 hours, median gap 10 minutes. "Behind the
405
405
  # newest Lisa on this disk" is the DEFAULT state of a checkout within
406
406
  # minutes of being cut, through nothing anyone did wrong.
407
- # - Across the 27 host checkouts in this fleet, 12 resolve guards at all, and
408
- # ALL TWELVE are behind or undateable. None is current. Eight carry no
409
- # apply receipt; four are behind by a whole MAJOR (3.23.0, 3.23.1, 3.46.3,
410
- # 3.54.4 against 4.17.2).
407
+ # - Measured once across the host checkouts on one fleet: of those that
408
+ # resolved guards at all, every one was behind or undateable and none was
409
+ # current, several by a whole MAJOR — while a larger number resolved NO
410
+ # guard at all.
411
+ #
412
+ # That second reading used to be written here as a set of counts, and that was
413
+ # the wrong place for it. A number in a comment is a measurement, not a monitor:
414
+ # it was true on the day it was taken and nothing re-took it, so the drift after
415
+ # that date was unobserved — which is the same defect the number documents. The
416
+ # counts are therefore not restated here. Re-derive them:
417
+ #
418
+ # Run these FROM A LISA MONOREPO CHECKOUT — the census and the fleet roster are
419
+ # Lisa-monorepo artifacts, and this comment travels into host projects where
420
+ # neither exists. The script wrapper builds `dist/` first, which a fresh clone
421
+ # does not have:
422
+ #
423
+ # bun run lisa:enforcement-census # roster on this machine
424
+ # bun run lisa:enforcement-census -- --redact # safe to quote publicly
425
+ #
426
+ # In a host project the local half of the same question is `lisa doctor`, which
427
+ # reports what THIS checkout resolves and needs no roster.
428
+ #
429
+ # The census reports and never gates, and it keeps "resolves NO guard" apart
430
+ # from "resolves something old" — different failures, different remedies, and
431
+ # folding them is what made the unenforced checkouts easy to miss
432
+ # (CodySwannGT/lisa#3490). `lisa doctor` carries the same finding for the single
433
+ # checkout it is run in.
411
434
  #
412
435
  # So no distance threshold rescues it — even the strictest version predicate is
413
436
  # permanently true across the fleet. The noise is the REPETITION, not the
@@ -87,6 +87,76 @@ if ! command -v node >/dev/null 2>&1; then
87
87
  echo "❌ Push blocked: Node.js is required to validate tracker work items."
88
88
  exit 1
89
89
  fi
90
+
91
+ # BEGIN: push destination guard
92
+ # ---------------------------------------------------------------------------
93
+ # Refuse a push whose RESOLVED destination is a deploy branch it was not aimed
94
+ # at.
95
+ #
96
+ # `push.default=upstream` (and its deprecated spelling `tracking`) resolves a
97
+ # push's destination from the branch's UPSTREAM rather than from the branch
98
+ # named on the command line. A working branch created the ordinary way —
99
+ # `git checkout -b <branch> origin/main` — has `main` as its upstream, so the
100
+ # ordinary `git push -u origin <branch>` resolves to `refs/heads/main` and lands
101
+ # there. The push reports success. Branch protection and every required check
102
+ # are bypassed, and the operator is told only afterwards, if at all
103
+ # (CodySwannGT/lisa#3495).
104
+ #
105
+ # Measured in Lisa's own repository: two commits reached the default branch this
106
+ # way. Every detection control afterwards fired correctly — the bypass was
107
+ # reported, an agent force-push was refused, the release run was caught and
108
+ # cancelled — and the commits shipped in a published release regardless, because
109
+ # an ordinary unrelated merge cut a release before anyone could rewind. Nothing
110
+ # had prevented the original push. That is why this is a refusal and not a
111
+ # warning.
112
+ #
113
+ # WHY HERE, and not folded into a check that already exists.
114
+ #
115
+ # It reads the RESOLVED DESTINATION, in the third field of the stdin lines. That
116
+ # is the only place in the system where the destination git actually chose is
117
+ # observable; `push.default`, the branch's upstream and the command line are all
118
+ # proxies for it, and the accident is precisely a disagreement between the
119
+ # proxies and the answer. So the check has to live at the push moment, on this
120
+ # stream.
121
+ #
122
+ # It is UNCONDITIONAL. The traceability step below stands down when a project
123
+ # declares `gates.traceability`, and a guard a declaration can switch off is not
124
+ # a guard against an accident. It also runs BELOW the deletion-only guard, which
125
+ # exits first, so deleting a branch still needs no node.
126
+ #
127
+ # WHAT IT ALLOWS, deliberately: `main -> main`, so a merge landed locally still
128
+ # pushes; a `HEAD` local ref, which is how git spells a destination the pusher
129
+ # wrote out in full and how release automation pushes from a detached checkout;
130
+ # and every push whose destination is not a deploy branch at all.
131
+ #
132
+ # READING THE STREAM SPENDS IT — the same constraint the deletion guard above
133
+ # documents, and the same remedy: capture, use, replay with `exec <`. If no
134
+ # scratch file can be created this stands down without reading a byte, leaving
135
+ # the hook exactly as it was; a guard that cannot read the stream must not also
136
+ # starve the checks that can.
137
+ # ---------------------------------------------------------------------------
138
+ #
139
+ # The `-f` test is not belt and braces. This is now the FIRST thing in the file
140
+ # to invoke the validator, so a checkout where no candidate path exists would
141
+ # fail every push with a Node module-resolution error instead of the hook's own
142
+ # message. Standing down leaves that diagnosis to the traceability step below,
143
+ # which owns it and words it properly.
144
+ LISA_DEST_REFS="$(mktemp "${TMPDIR:-/tmp}/lisa-dest-refs.XXXXXXXX")" || LISA_DEST_REFS=""
145
+ if [ -n "$LISA_DEST_REFS" ] && [ -f "$WORK_ITEM_SCRIPT" ]; then
146
+ cat > "$LISA_DEST_REFS"
147
+ if ! node "$WORK_ITEM_SCRIPT" validate-push-destination "${1:-origin}" --refs "$LISA_DEST_REFS"; then
148
+ rm -f "$LISA_DEST_REFS"
149
+ exit 1
150
+ fi
151
+ # Put the stream back, byte for byte, for the validator and every gate below.
152
+ exec < "$LISA_DEST_REFS"
153
+ # Unlinked while the descriptor above is still open, so nothing is left in
154
+ # TMPDIR and no later `trap ... EXIT` in this file can drop the cleanup.
155
+ rm -f "$LISA_DEST_REFS"
156
+ fi
157
+ unset LISA_DEST_REFS
158
+ # END: push destination guard
159
+
90
160
  # ---------------------------------------------------------------------------
91
161
  # Work-item traceability, at the push moment.
92
162
  #