@codyswann/lisa 4.4.1 → 4.4.2

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 (64) hide show
  1. package/dist/core/upstream-evidence-manifest.js +12 -12
  2. package/package.json +1 -1
  3. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  4. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  5. package/plugins/lisa-agy/plugin.json +1 -1
  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-cdk-agy/plugin.json +1 -1
  9. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  10. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  11. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  12. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  13. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  14. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  15. package/plugins/lisa-expo-agy/plugin.json +1 -1
  16. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  17. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  18. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  19. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  20. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  21. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  22. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  24. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  25. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  26. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  27. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  28. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  29. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  30. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  31. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  32. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  34. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  35. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  36. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  40. package/plugins/lisa-rails-agy/plugin.json +1 -1
  41. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  45. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  46. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  50. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  51. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  53. package/scripts/check-delivery-deletion-conflicts.mjs +2 -2
  54. package/scripts/check-derived-artifacts.mjs +24 -5
  55. package/scripts/check-orphan-test-processes.mjs +2 -2
  56. package/scripts/check-security-floors.mjs +20 -9
  57. package/scripts/check-template-workflow-refs.mjs +23 -8
  58. package/scripts/check-workflow-package-paths.mjs +12 -8
  59. package/scripts/generate-lisa-owned-hash-ledger.mjs +12 -4
  60. package/scripts/generate-upstream-evidence-manifest.mjs +4 -4
  61. package/scripts/verify-health-agentic-built.mjs +4 -3
  62. package/scripts/verify-health-consumer-built.mjs +3 -2
  63. package/scripts/verify-health-contract-built.mjs +3 -2
  64. package/scripts/verify-health-deterministic-built.mjs +5 -4
@@ -1073,18 +1073,18 @@ export const UPSTREAM_EVIDENCE_MANIFEST = Object.freeze({
1073
1073
  "rails/github-rulesets/quality-checks.json": "0ec1c366589edbaa6b8f45f940cef3ada5ed7b1a956d98d3fa6034b0f78fbc7c",
1074
1074
  "rails/merge/.claude/settings.json": "9c49f8c7c453f8749c90def3e22d412c3345c533d24b30dc7745ffa052ad6fa1",
1075
1075
  "scripts/build-plugins.sh": "e405d9ae41c361f77fcf60a6ea4054b3f927c663131fa661295a70c911e11189",
1076
- "scripts/check-delivery-deletion-conflicts.mjs": "e7fe0064ad82a4dba17436657630a46891568a3d18930eb172fe9ee3f54196aa",
1077
- "scripts/check-derived-artifacts.mjs": "86cca46aa7f055ae149b5a378d56d2c4e5dbbbe48e020b79883e21cae50c5956",
1076
+ "scripts/check-delivery-deletion-conflicts.mjs": "77eed58c9ff076c4438a5fe01c343f25c1a1c8f29a8f5861d1e1081b1739d300",
1077
+ "scripts/check-derived-artifacts.mjs": "2911e2153f6accceacec81fa0255afe9b75cc58c69476db1505eb08878f6672d",
1078
1078
  "scripts/check-duplicate-versions.mjs": "26b26351dd2735dfd8d9a2436b89e876ce2ed93871d0b264f611215dd03d70ae",
1079
1079
  "scripts/check-learnings-budget.ts": "0112c857910880a5e53c4fa6bccc3ad7f060854d3402cb36bd81216181e54160",
1080
- "scripts/check-orphan-test-processes.mjs": "7f2144154c19d3f4e1a1a42c5a6c177010090eb1ab0fe1fac8481d36b8e4336d",
1080
+ "scripts/check-orphan-test-processes.mjs": "123822f43b53a84b619ed97d5c18069feb5964b8216248968a7ab165e345106a",
1081
1081
  "scripts/check-plugins-sync.sh": "3a99cbff0e8ec5e7c944690eae003a6d51b51c85436d77bd37faea4d553728af",
1082
1082
  "scripts/check-required-check-promotions.mjs": "ae7a3d80beb98d8069af12d2d588e76f73f7a329b1f883ddbe1818bb1b5cafa9",
1083
1083
  "scripts/check-rules-pairing.sh": "4d4a0d9e8d36794a22020f419c879d7336b1c5bfe883acdcc826d26764560c7a",
1084
- "scripts/check-security-floors.mjs": "330d0a68b455ca56faaebf4d0645c919cd5c175fe7a5c4171fe1dc9265cc4488",
1084
+ "scripts/check-security-floors.mjs": "6122404ea247c7d70718472e44bb007c0c5dad2fcc064c8a2d797cd9d0f85b29",
1085
1085
  "scripts/check-state-classification.mjs": "26baaa85c0758fa41f1f66257f7d3a31ea4dd5cf17a9c0acaa3e89caaaf6265a",
1086
- "scripts/check-template-workflow-refs.mjs": "659f2f11f20ea8414d1732b90f388c937a9b628f8689f02a7fbaa93666e6bd2f",
1087
- "scripts/check-workflow-package-paths.mjs": "2392ac6f59e1b2036908a8a20dcfe2516fbf72613683c7232a0f2ab271f20410",
1086
+ "scripts/check-template-workflow-refs.mjs": "f784a822ae82f41b7501d61e45a1badeecb84f118083f4c53c66402d6c50ffa2",
1087
+ "scripts/check-workflow-package-paths.mjs": "1777c3aea3f3642e1ab2fd64966cca7ea21bf5330b3e5697cde3122c9189e44e",
1088
1088
  "scripts/claude-remote-setup.sh": "0e33accf8aa057c70497f01c38bef9f9f3801d649272f583578d89201b242655",
1089
1089
  "scripts/clean-dist.mjs": "04970d768464acbf96ebeb77c16a6a3b3da56c744eef0da64c573c6152ee0dc7",
1090
1090
  "scripts/cleanup-amplify-branches.sh": "eb9db90b07818dd156dd48d04b6350a10b39be8ffe0267d72cc13705c8eb5350",
@@ -1101,8 +1101,8 @@ export const UPSTREAM_EVIDENCE_MANIFEST = Object.freeze({
1101
1101
  "scripts/generate-codex-plugin-artifacts.mjs": "facb86910c07d1b8025868d24425fdaea7b6efbe282bace5ee5bf23957d6b51a",
1102
1102
  "scripts/generate-copilot-plugin-artifacts.mjs": "7af923118c49101e6ba46da73a8eee8aee9b797e074618aa5345913e638d7cdb",
1103
1103
  "scripts/generate-cursor-plugin-artifacts.mjs": "45d3bde7b6c55310f0d9436dd68a1bdeefb6fa817be4d15ef745421b39a2cf47",
1104
- "scripts/generate-lisa-owned-hash-ledger.mjs": "4e7a6dd9325fff77faec49c1dcbf8a8cfeb987052eeed89885deb4f7b0a107bc",
1105
- "scripts/generate-upstream-evidence-manifest.mjs": "a33bd0ee344782a9b2f400f5adfeecd9e7e201b61b073a9e85cc69311b8721e0",
1104
+ "scripts/generate-lisa-owned-hash-ledger.mjs": "7f6c5456c50cd3ab33f528eff40a13f501521bbe6dd0e068afd599e5da1c1f89",
1105
+ "scripts/generate-upstream-evidence-manifest.mjs": "8f18972834d6be9771501c35da30de95539a7e7b3fb3d7605e0bc31477812a5f",
1106
1106
  "scripts/github-status-check.sh": "c6a4a13ff5cf689fcbd7a6aca718f9e7d46d54ccb6687f662b055c1ca20e792f",
1107
1107
  "scripts/install-claude-plugins.sh": "0e96ebbae1e59304bb432286eccba38900a6008b80f2c53a1ec2c95801d4087c",
1108
1108
  "scripts/internal-agy-skill-policy.json": "c2ce87d2eeebfdc9f24d6486425c010cf9289376f8a459f4767ca22d2bf8670d",
@@ -1141,10 +1141,10 @@ export const UPSTREAM_EVIDENCE_MANIFEST = Object.freeze({
1141
1141
  "scripts/test-intent-routing.sh": "97b9dc86cbd805df8a7fdb8c99ffab9b8c5e751ba2e84c05420b2a124f80635d",
1142
1142
  "scripts/update-node-version.ts": "dcfec9f8666f65925deb9efdc8a4bebb6c3f41d9fa1c1365be23edb82c40fffa",
1143
1143
  "scripts/update-test-skill-paths.mjs": "e26a3ac716c33013e22bf5223a58457452f5af2038218a0e3a51c244eab5ddc9",
1144
- "scripts/verify-health-agentic-built.mjs": "0681576a7fdaf571ed9ee870f6475b4bf921ebb9a3b1eb8f1bc9db9551683c3b",
1145
- "scripts/verify-health-consumer-built.mjs": "f6cc2a931bd6812fd82a2b518ea1fba074633cc100710409c945190dc1092333",
1146
- "scripts/verify-health-contract-built.mjs": "5ebfec9b8a0e3004a5af65d2f2515526f7ff5817e61ae30042fb8be40d8f8d17",
1147
- "scripts/verify-health-deterministic-built.mjs": "0141da2227297d613c5e695abcb4d94d410409980b52d16104787a998035ad21",
1144
+ "scripts/verify-health-agentic-built.mjs": "4b0d20b2c94390ff6f5af881fc50c5c225215a282891db0b3e3d84a7e6455367",
1145
+ "scripts/verify-health-consumer-built.mjs": "bd0676f7e76e4ebceb3a94c89a2b40f4c0266ab0ef7280a9bebbcb388c2731c3",
1146
+ "scripts/verify-health-contract-built.mjs": "4041780d412bee7b9e1bbe24d908bac64705184a8b0698f0d5bb748ec36b0bcd",
1147
+ "scripts/verify-health-deterministic-built.mjs": "9016663d16c93d0d0d74b41a957d200cd3aa8ef5b12e0879d24ba5ea041ab307",
1148
1148
  "scripts/verify-learner-frontmatter-built.mjs": "182e5e99f52257d3079ee1bc42b45c3dcca8b93a968205ddb3f3af26f2558b47",
1149
1149
  "tsconfig/base.json": "d04a105ec81aa9dc69d230d4a83406d920c305b6fc13c641ebafde0c41fc658f",
1150
1150
  "tsconfig/build.json": "a743350ea93b0e8d2151c974c5a8a4cbcf946cd7f06c40d83146079bde70afe1",
package/package.json CHANGED
@@ -145,7 +145,7 @@
145
145
  "zod-validation-error": "^4.0.0"
146
146
  },
147
147
  "name": "@codyswann/lisa",
148
- "version": "4.4.1",
148
+ "version": "4.4.2",
149
149
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
150
150
  "main": "dist/index.js",
151
151
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.4.1",
3
+ "version": "4.4.2",
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": "4.4.1",
3
+ "version": "4.4.2",
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": "4.4.1",
3
+ "version": "4.4.2",
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-cdk",
3
- "version": "4.4.1",
3
+ "version": "4.4.2",
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": "4.4.1",
3
+ "version": "4.4.2",
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-cdk",
3
- "version": "4.4.1",
3
+ "version": "4.4.2",
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": "4.4.1",
3
+ "version": "4.4.2",
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": "4.4.1",
3
+ "version": "4.4.2",
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",
3
- "version": "4.4.1",
3
+ "version": "4.4.2",
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": "4.4.1",
3
+ "version": "4.4.2",
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-expo",
3
- "version": "4.4.1",
3
+ "version": "4.4.2",
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": "4.4.1",
3
+ "version": "4.4.2",
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-expo",
3
- "version": "4.4.1",
3
+ "version": "4.4.2",
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": "4.4.1",
3
+ "version": "4.4.2",
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": "4.4.1",
3
+ "version": "4.4.2",
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-harper-fabric",
3
- "version": "4.4.1",
3
+ "version": "4.4.2",
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": "4.4.1",
3
+ "version": "4.4.2",
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-harper-fabric",
3
- "version": "4.4.1",
3
+ "version": "4.4.2",
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": "4.4.1",
3
+ "version": "4.4.2",
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": "4.4.1",
3
+ "version": "4.4.2",
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-nestjs",
3
- "version": "4.4.1",
3
+ "version": "4.4.2",
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": "4.4.1",
3
+ "version": "4.4.2",
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-nestjs",
3
- "version": "4.4.1",
3
+ "version": "4.4.2",
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": "4.4.1",
3
+ "version": "4.4.2",
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": "4.4.1",
3
+ "version": "4.4.2",
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-openclaw",
3
- "version": "4.4.1",
3
+ "version": "4.4.2",
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": "4.4.1",
3
+ "version": "4.4.2",
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-openclaw",
3
- "version": "4.4.1",
3
+ "version": "4.4.2",
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": "4.4.1",
3
+ "version": "4.4.2",
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": "4.4.1",
3
+ "version": "4.4.2",
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-phaser",
3
- "version": "4.4.1",
3
+ "version": "4.4.2",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.4.1",
3
+ "version": "4.4.2",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.4.1",
3
+ "version": "4.4.2",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.4.1",
3
+ "version": "4.4.2",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "4.4.1",
3
+ "version": "4.4.2",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "4.4.1",
3
+ "version": "4.4.2",
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": "4.4.1",
3
+ "version": "4.4.2",
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-rails",
3
- "version": "4.4.1",
3
+ "version": "4.4.2",
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": "4.4.1",
3
+ "version": "4.4.2",
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": "4.4.1",
3
+ "version": "4.4.2",
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-typescript",
3
- "version": "4.4.1",
3
+ "version": "4.4.2",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "4.4.1",
3
+ "version": "4.4.2",
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-typescript",
3
- "version": "4.4.1",
3
+ "version": "4.4.2",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "4.4.1",
3
+ "version": "4.4.2",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "4.4.1",
3
+ "version": "4.4.2",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.4.1",
3
+ "version": "4.4.2",
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": "4.4.1",
3
+ "version": "4.4.2",
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"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "4.4.1",
3
+ "version": "4.4.2",
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": "4.4.1",
3
+ "version": "4.4.2",
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": "4.4.1",
3
+ "version": "4.4.2",
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"
@@ -82,12 +82,12 @@
82
82
  *
83
83
  * @module scripts/check-delivery-deletion-conflicts
84
84
  */
85
- import { execFileSync } from "node:child_process";
86
85
  import fs from "node:fs";
87
86
  import path from "node:path";
88
87
  import process from "node:process";
89
88
  import { fileURLToPath } from "node:url";
90
89
 
90
+ import { boundedExecFileSync } from "./lib/bounded-spawn.mjs";
91
91
  import { invokedAsScript } from "./lib/invoked-as-script.mjs";
92
92
 
93
93
  const REPO_ROOT = path.resolve(
@@ -250,7 +250,7 @@ export function matchDeletion(delivered, deleted) {
250
250
  function listTrackedFiles(root) {
251
251
  let stdout;
252
252
  try {
253
- stdout = execFileSync("git", ["-C", root, "ls-files", "-z"], {
253
+ stdout = boundedExecFileSync("git", ["-C", root, "ls-files", "-z"], {
254
254
  encoding: "utf8",
255
255
  maxBuffer: MAX_GIT_OUTPUT_BYTES,
256
256
  stdio: ["ignore", "pipe", "ignore"],
@@ -44,10 +44,10 @@
44
44
  * on a commit that owed nothing, which is the safe direction to be wrong in.
45
45
  * @module scripts/check-derived-artifacts
46
46
  */
47
- import { execFileSync } from "node:child_process";
48
47
  import path from "node:path";
49
48
  import process from "node:process";
50
49
 
50
+ import { boundedExecFileSync, isChildTimeout } from "./lib/bounded-spawn.mjs";
51
51
  import { invokedAsScript } from "./lib/invoked-as-script.mjs";
52
52
 
53
53
  const repoRoot = path.resolve(import.meta.dirname, "..");
@@ -118,18 +118,26 @@ export function requiresManifestCheck(stagedPaths) {
118
118
  * Renames report their destination (`-M` plus `--diff-filter=R`), and deletions
119
119
  * are included because removing a template is also a way to move what the
120
120
  * artifacts describe.
121
- * @returns {string[]} Staged paths, empty when git cannot be consulted
121
+ *
122
+ * A KILLED child re-raises rather than returning empty. An empty list means
123
+ * "nothing staged touches the artifacts", which makes this guard SKIP — so a
124
+ * busy machine would let through the commit that moves a template without
125
+ * regenerating what describes it. "I could not ask" is not "there is nothing
126
+ * to check", and only one of those is safe to act on.
127
+ * @returns {string[]} Staged paths, empty when git said there were none
128
+ * @throws {Error} When the child was killed at its deadline
122
129
  */
123
130
  export function stagedPaths() {
124
131
  try {
125
- return execFileSync(
132
+ return boundedExecFileSync(
126
133
  "git",
127
134
  ["diff", "--cached", "--name-only", "--diff-filter=ACMRD"],
128
135
  { cwd: repoRoot, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
129
136
  )
130
137
  .split("\n")
131
138
  .filter(Boolean);
132
- } catch {
139
+ } catch (error) {
140
+ if (isChildTimeout(error)) throw error;
133
141
  return [];
134
142
  }
135
143
  }
@@ -172,13 +180,24 @@ export function readableFailure(raw) {
172
180
  */
173
181
  function runCheck(script) {
174
182
  try {
175
- execFileSync(process.execPath, [script, "--check"], {
183
+ boundedExecFileSync(process.execPath, [script, "--check"], {
176
184
  cwd: repoRoot,
177
185
  encoding: "utf8",
178
186
  stdio: ["ignore", "pipe", "pipe"],
179
187
  });
180
188
  return { ok: true, output: "" };
181
189
  } catch (error) {
190
+ // Already fail-closed — `ok: false` fails the guard either way — so the
191
+ // catch stays. What it must NOT do is stay silent about a kill: a killed
192
+ // child hands back EMPTY streams, so a timeout would otherwise render as a
193
+ // generator that failed with no diagnostic, which reads as a content
194
+ // problem and sends the operator to the wrong place entirely.
195
+ if (isChildTimeout(error)) {
196
+ return {
197
+ ok: false,
198
+ output: `${script} was killed before it finished. This is a timeout, not a check failure — nothing about the artifact was measured.`,
199
+ };
200
+ }
182
201
  const stderr = error?.stderr ?? "";
183
202
  const stdout = error?.stdout ?? "";
184
203
  return { ok: false, output: readableFailure(`${stdout}${stderr}`) };
@@ -44,8 +44,8 @@
44
44
  *
45
45
  * @module scripts/check-orphan-test-processes
46
46
  */
47
- import { execFileSync } from "node:child_process";
48
47
  import process from "node:process";
48
+ import { boundedExecFileSync } from "./lib/bounded-spawn.mjs";
49
49
  import { invokedAsScript } from "./lib/invoked-as-script.mjs";
50
50
 
51
51
  /**
@@ -168,7 +168,7 @@ export function selectOrphans(rows, minAgeSeconds) {
168
168
  */
169
169
  function readProcessTable() {
170
170
  // eslint-disable-next-line sonarjs/no-os-command-from-path -- fixed executable and argv
171
- return execFileSync("ps", ["-eo", "pid,ppid,etime,command"], {
171
+ return boundedExecFileSync("ps", ["-eo", "pid,ppid,etime,command"], {
172
172
  encoding: "utf8",
173
173
  maxBuffer: 32 * 1024 * 1024,
174
174
  });
@@ -23,10 +23,11 @@
23
23
  * @module scripts/check-security-floors
24
24
  */
25
25
 
26
- import { execFileSync } from "node:child_process";
27
26
  import { existsSync, readFileSync } from "node:fs";
28
27
  import { globSync } from "node:fs";
29
28
 
29
+ import { boundedExecFileSync } from "./lib/bounded-spawn.mjs";
30
+
30
31
  /**
31
32
  * Fixed absolute git locations, tried in order rather than a bare command name
32
33
  * so a writeable directory early on `PATH` cannot decide which binary runs.
@@ -129,18 +130,28 @@ const NOT_A_MANIFEST = [
129
130
  /**
130
131
  * Every manifest git tracks, so the glob can be checked against reality.
131
132
  *
132
- * Returns null when the question cannot be asked — outside a checkout, or with
133
- * no git. A null is reported as "not reconciled", never folded into "nothing
134
- * missing": the whole defect being fixed here is a scan that narrowed silently.
133
+ * Returns null when the question cannot be asked — outside a checkout, with no
134
+ * git, or with the child killed at its deadline. A null is reported as "not
135
+ * reconciled", never folded into "nothing missing": the whole defect being
136
+ * fixed here is a scan that narrowed silently.
137
+ *
138
+ * The swallow is therefore KEPT for a timeout rather than kept by inheritance.
139
+ * A kill is one more way the question cannot be asked, and this function
140
+ * already routes "cannot ask" to the safe answer — so re-raising would trade a
141
+ * correct report for a crash and gain nothing.
135
142
  * @returns {string[]|null} Tracked manifest paths, or null when unknowable.
136
143
  */
137
144
  function trackedManifests() {
138
145
  try {
139
- return execFileSync(GIT_BIN, ["ls-files", "-z", "*package.lisa.json"], {
140
- encoding: "utf8",
141
- maxBuffer: 8 * 1024 * 1024,
142
- stdio: ["ignore", "pipe", "ignore"],
143
- })
146
+ return boundedExecFileSync(
147
+ GIT_BIN,
148
+ ["ls-files", "-z", "*package.lisa.json"],
149
+ {
150
+ encoding: "utf8",
151
+ maxBuffer: 8 * 1024 * 1024,
152
+ stdio: ["ignore", "pipe", "ignore"],
153
+ }
154
+ )
144
155
  .split("\0")
145
156
  .filter(Boolean)
146
157
  .filter(
@@ -57,12 +57,12 @@
57
57
  *
58
58
  * @module scripts/check-template-workflow-refs
59
59
  */
60
- import { execFileSync } from "node:child_process";
61
60
  import fs from "node:fs";
62
61
  import path from "node:path";
63
62
  import process from "node:process";
64
63
  import { fileURLToPath } from "node:url";
65
64
 
65
+ import { boundedExecFileSync, isChildTimeout } from "./lib/bounded-spawn.mjs";
66
66
  import { invokedAsScript } from "./lib/invoked-as-script.mjs";
67
67
 
68
68
  const REPO_ROOT = path.resolve(
@@ -124,7 +124,7 @@ export function findWorkflowRefs(content) {
124
124
  function listTrackedFiles(root) {
125
125
  let stdout;
126
126
  try {
127
- stdout = execFileSync("git", ["-C", root, "ls-files", "-z"], {
127
+ stdout = boundedExecFileSync("git", ["-C", root, "ls-files", "-z"], {
128
128
  encoding: "utf8",
129
129
  maxBuffer: MAX_GIT_OUTPUT_BYTES,
130
130
  stdio: ["ignore", "pipe", "ignore"],
@@ -140,13 +140,19 @@ function listTrackedFiles(root) {
140
140
  /**
141
141
  * Whether `ref` names an object present in this clone.
142
142
  *
143
+ * A KILLED child re-raises. `false` here does not mean "unknown", it means
144
+ * "this reference is broken" — a verdict the caller reports. So a busy machine
145
+ * would manufacture a failure about a reference that is perfectly fine, and a
146
+ * guard that invents failures is one people learn to re-run rather than read.
147
+ * That is the sibling harm to failing open, and it is just as corrosive.
143
148
  * @param {string} root - the repository root.
144
149
  * @param {string} ref - a tag, branch, or SHA.
145
150
  * @returns {boolean} whether the ref resolves locally.
151
+ * @throws {Error} When the child was killed at its deadline
146
152
  */
147
153
  function refExists(root, ref) {
148
154
  try {
149
- execFileSync(
155
+ boundedExecFileSync(
150
156
  "git",
151
157
  ["-C", root, "rev-parse", "--verify", `${ref}^{commit}`],
152
158
  {
@@ -154,7 +160,8 @@ function refExists(root, ref) {
154
160
  }
155
161
  );
156
162
  return true;
157
- } catch {
163
+ } catch (error) {
164
+ if (isChildTimeout(error)) throw error;
158
165
  return false;
159
166
  }
160
167
  }
@@ -162,18 +169,26 @@ function refExists(root, ref) {
162
169
  /**
163
170
  * Whether `target` exists in the tree named by `ref`.
164
171
  *
172
+ * A KILLED child re-raises, for the reason given on `refExists` above: `false`
173
+ * is a verdict of "absent at that ref", not an admission of ignorance.
165
174
  * @param {string} root - the repository root.
166
175
  * @param {string} ref - a tag, branch, or SHA known to resolve.
167
176
  * @param {string} target - a repo-relative path.
168
177
  * @returns {boolean} whether the path exists at that ref.
178
+ * @throws {Error} When the child was killed at its deadline
169
179
  */
170
180
  function pathExistsAtRef(root, ref, target) {
171
181
  try {
172
- execFileSync("git", ["-C", root, "cat-file", "-e", `${ref}:${target}`], {
173
- stdio: ["ignore", "ignore", "ignore"],
174
- });
182
+ boundedExecFileSync(
183
+ "git",
184
+ ["-C", root, "cat-file", "-e", `${ref}:${target}`],
185
+ {
186
+ stdio: ["ignore", "ignore", "ignore"],
187
+ }
188
+ );
175
189
  return true;
176
- } catch {
190
+ } catch (error) {
191
+ if (isChildTimeout(error)) throw error;
177
192
  return false;
178
193
  }
179
194
  }
@@ -77,7 +77,6 @@
77
77
  *
78
78
  * @module scripts/check-workflow-package-paths
79
79
  */
80
- import { execFileSync } from "node:child_process";
81
80
  import {
82
81
  existsSync,
83
82
  mkdirSync,
@@ -89,6 +88,7 @@ import { mkdtempSync } from "node:fs";
89
88
  import { tmpdir } from "node:os";
90
89
  import path from "node:path";
91
90
 
91
+ import { boundedExecFileSync } from "./lib/bounded-spawn.mjs";
92
92
  import { invokedAsScript } from "./lib/invoked-as-script.mjs";
93
93
 
94
94
  /** The package whose released layout a workflow may make claims about. */
@@ -179,7 +179,7 @@ function parseArgs(argv) {
179
179
  * @returns {readonly string[]} Repo-relative workflow paths
180
180
  */
181
181
  function trackedWorkflows(root) {
182
- const output = execFileSync(
182
+ const output = boundedExecFileSync(
183
183
  "git",
184
184
  ["-C", root, "ls-files", ".github/workflows"],
185
185
  { encoding: "utf8" }
@@ -285,7 +285,7 @@ export function collectClaims(root) {
285
285
  */
286
286
  function publishedVersions() {
287
287
  const versions = JSON.parse(
288
- execFileSync("npm", ["view", PACKAGE_NAME, "versions", "--json"], {
288
+ boundedExecFileSync("npm", ["view", PACKAGE_NAME, "versions", "--json"], {
289
289
  encoding: "utf8",
290
290
  stdio: ["ignore", "pipe", "pipe"],
291
291
  maxBuffer: 16 * 1024 * 1024,
@@ -357,7 +357,7 @@ function fetchReleaseListings(chosen, cacheDir) {
357
357
  continue;
358
358
  }
359
359
  const staging = mkdtempSync(path.join(tmpdir(), "lisa-release-"));
360
- execFileSync(
360
+ boundedExecFileSync(
361
361
  "npm",
362
362
  ["pack", `${PACKAGE_NAME}@${version}`, "--pack-destination", staging],
363
363
  { stdio: ["ignore", "pipe", "pipe"] }
@@ -366,10 +366,14 @@ function fetchReleaseListings(chosen, cacheDir) {
366
366
  if (tarball === undefined) {
367
367
  throw new Error(`npm pack produced no tarball for ${version}`);
368
368
  }
369
- const entries = execFileSync("tar", ["-tzf", path.join(staging, tarball)], {
370
- encoding: "utf8",
371
- maxBuffer: 64 * 1024 * 1024,
372
- })
369
+ const entries = boundedExecFileSync(
370
+ "tar",
371
+ ["-tzf", path.join(staging, tarball)],
372
+ {
373
+ encoding: "utf8",
374
+ maxBuffer: 64 * 1024 * 1024,
375
+ }
376
+ )
373
377
  .split("\n")
374
378
  .filter(Boolean);
375
379
  listings[version] = entries;
@@ -19,11 +19,12 @@
19
19
  * legitimately stale file as host-modified and stop refreshing it — failing
20
20
  * safe, but permanently.
21
21
  */
22
- import { execFileSync } from "node:child_process";
23
22
  import { createHash } from "node:crypto";
24
23
  import { readFileSync, writeFileSync } from "node:fs";
25
24
  import path from "node:path";
26
25
 
26
+ import { boundedExecFileSync, isChildTimeout } from "./lib/bounded-spawn.mjs";
27
+
27
28
  const repoRoot = path.resolve(import.meta.dirname, "..");
28
29
  const outputPath = path.join(repoRoot, "src/core/lisa-owned-hash-ledger.ts");
29
30
  const COPY_OVERWRITE = "copy-overwrite";
@@ -35,18 +36,25 @@ const ENFORCEMENT_TREE = "scripts/";
35
36
  *
36
37
  * Blobs are read as `binary` so arbitrary bytes survive the round trip into the
37
38
  * Buffer that gets hashed; decoding as UTF-8 would corrupt any non-text file.
39
+ * A KILLED child re-raises. Every caller reads this as content, and an empty
40
+ * string is a perfectly well-formed answer meaning "no files" — so a timeout
41
+ * on the `ls-files` call would build a ledger that vouches for NOTHING and
42
+ * report it as a complete one. A ledger's whole job is to vouch for bytes; one
43
+ * assembled from silence is worse than none, because it looks authoritative.
38
44
  * @param {readonly string[]} args - Arguments passed to git
39
- * @returns {string} Command stdout, or an empty string when git fails
45
+ * @returns {string} Command stdout, or an empty string when git said nothing
46
+ * @throws {Error} When the child was killed at its deadline
40
47
  */
41
48
  function git(args) {
42
49
  try {
43
- return execFileSync("git", args, {
50
+ return boundedExecFileSync("git", args, {
44
51
  cwd: repoRoot,
45
52
  encoding: "buffer",
46
53
  maxBuffer: 64 * 1024 * 1024,
47
54
  stdio: ["ignore", "pipe", "ignore"],
48
55
  }).toString("binary");
49
- } catch {
56
+ } catch (error) {
57
+ if (isChildTimeout(error)) throw error;
50
58
  return "";
51
59
  }
52
60
  }
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  /** Generate the exact hash-pinned allowlist for public upstream evidence. */
3
- import { execFileSync } from "node:child_process";
4
3
  import { createHash } from "node:crypto";
5
4
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
6
5
  import path from "node:path";
7
6
 
7
+ import { boundedExecFileSync } from "./lib/bounded-spawn.mjs";
8
8
  import {
9
9
  describeStaleManifest,
10
10
  diagnoseStaleManifest,
@@ -86,7 +86,7 @@ function repositoryPath(url) {
86
86
  // comparison is by path: `url.<base>.insteadOf` rewrites what `git remote
87
87
  // get-url` returns, so it answers "how do I reach this" rather than "what is
88
88
  // this". Falls back to the resolved URL only if no value is configured.
89
- const originUrl = execFileSync(
89
+ const originUrl = boundedExecFileSync(
90
90
  "git",
91
91
  ["config", "--get", "remote.origin.url"],
92
92
  { cwd: repoRoot, encoding: "utf8" }
@@ -100,7 +100,7 @@ if (repositoryPath(originUrl) !== canonicalRepository.toLowerCase()) {
100
100
  );
101
101
  }
102
102
 
103
- const tracked = execFileSync("git", ["ls-files", "-z"], {
103
+ const tracked = boundedExecFileSync("git", ["ls-files", "-z"], {
104
104
  cwd: repoRoot,
105
105
  encoding: "utf8",
106
106
  })
@@ -135,7 +135,7 @@ const existingPublicCommits = [
135
135
  .filter(Boolean);
136
136
  const refreshPublicCommits = process.argv.includes("--refresh-public-commits");
137
137
  const discoveredPublicCommits = refreshPublicCommits
138
- ? execFileSync("git", ["rev-list", "--remotes=origin"], {
138
+ ? boundedExecFileSync("git", ["rev-list", "--remotes=origin"], {
139
139
  cwd: repoRoot,
140
140
  encoding: "utf8",
141
141
  })
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /** Empirical proof for the shipped optional agentic Health composition API. */
3
3
  import assert from "node:assert/strict";
4
- import { execFileSync } from "node:child_process";
5
4
  import { createHash } from "node:crypto";
6
5
  import {
7
6
  lstat,
@@ -17,6 +16,8 @@ import {
17
16
  import { tmpdir } from "node:os";
18
17
  import path from "node:path";
19
18
 
19
+ import { boundedExecFileSync } from "./lib/bounded-spawn.mjs";
20
+
20
21
  // Literals named once — each was repeated enough times that a typo in one
21
22
  // copy would diverge silently.
22
23
  const TEMPLATES_MANAGED = "templates.managed";
@@ -62,14 +63,14 @@ const root = process.cwd();
62
63
  const workspace = await mkdtemp(path.join(tmpdir(), "lisa-health-agentic-"));
63
64
  const project = path.join(workspace, "host");
64
65
  const isolatedBin = path.join(workspace, "bin");
65
- const gitExecutable = execFileSync("which", ["git"], {
66
+ const gitExecutable = boundedExecFileSync("which", ["git"], {
66
67
  encoding: "utf8",
67
68
  }).trim();
68
69
  const healthFile = path.join(project, ".lisa", "health", "latest.json");
69
70
  const startedAt = Date.now();
70
71
 
71
72
  const run = (executable, argv, options = {}) =>
72
- execFileSync(executable, argv, {
73
+ boundedExecFileSync(executable, argv, {
73
74
  cwd: project,
74
75
  encoding: "utf8",
75
76
  stdio: ["ignore", "pipe", "pipe"],
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /** Built-package proof for the real two-phase Health CLI consumer. */
3
3
  import assert from "node:assert/strict";
4
- import { execFileSync } from "node:child_process";
5
4
  import {
6
5
  lstat,
7
6
  mkdir,
@@ -14,6 +13,8 @@ import {
14
13
  import { tmpdir } from "node:os";
15
14
  import path from "node:path";
16
15
 
16
+ import { boundedExecFileSync } from "./lib/bounded-spawn.mjs";
17
+
17
18
  // Literals named once — each was repeated enough times that a typo in one
18
19
  // copy would diverge silently.
19
20
  const LATEST_JSON = "latest.json";
@@ -53,7 +54,7 @@ async function createProject(name) {
53
54
 
54
55
  /** Run the compiled CLI and capture its exact standard-output bytes. */
55
56
  function runCli(project, argv, input) {
56
- return execFileSync(
57
+ return boundedExecFileSync(
57
58
  process.execPath,
58
59
  [cli, "--no-update-check", "health", ...argv, project],
59
60
  {
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  /** Empirical proof for the shipped Health v1 contract and real built CLI. */
3
3
  import assert from "node:assert/strict";
4
- import { execFileSync } from "node:child_process";
5
4
  import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
6
5
  import { tmpdir } from "node:os";
7
6
  import path from "node:path";
8
7
 
8
+ import { boundedExecFileSync } from "./lib/bounded-spawn.mjs";
9
+
9
10
  let assertions = 0;
10
11
  const check = (condition, message) => {
11
12
  assertions += 1;
@@ -23,7 +24,7 @@ try {
23
24
  path.join(project, ".lisa.config.json"),
24
25
  `${JSON.stringify({ tracker: "github", github: { org: "acme", repo: "app" } }, null, 2)}\n`
25
26
  );
26
- execFileSync(
27
+ boundedExecFileSync(
27
28
  process.execPath,
28
29
  [path.join(root, "dist/index.js"), "sync", project, "--json"],
29
30
  {
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  /** Empirical proof for the shipped deterministic Health fast path. */
3
3
  import assert from "node:assert/strict";
4
- import { execFileSync } from "node:child_process";
5
4
  import { createHash } from "node:crypto";
6
5
  import {
7
6
  chmod,
@@ -18,6 +17,8 @@ import {
18
17
  import { tmpdir } from "node:os";
19
18
  import path from "node:path";
20
19
 
20
+ import { boundedExecFileSync } from "./lib/bounded-spawn.mjs";
21
+
21
22
  // Literals named once — each was repeated enough times that a typo in one
22
23
  // copy would diverge silently.
23
24
  const PACKAGE_JSON = "package.json";
@@ -40,14 +41,14 @@ const root = process.cwd();
40
41
  const workspace = await mkdtemp(path.join(tmpdir(), "lisa-health-fast-path-"));
41
42
  const project = path.join(workspace, "host");
42
43
  const isolatedBin = path.join(workspace, "bin");
43
- const gitExecutable = execFileSync("which", ["git"], {
44
+ const gitExecutable = boundedExecFileSync("which", ["git"], {
44
45
  encoding: "utf8",
45
46
  }).trim();
46
47
  const healthFile = path.join(project, ".lisa", "health", "latest.json");
47
48
  const startedAt = Date.now();
48
49
 
49
50
  const run = (executable, argv, options = {}) =>
50
- execFileSync(executable, argv, {
51
+ boundedExecFileSync(executable, argv, {
51
52
  cwd: project,
52
53
  encoding: "utf8",
53
54
  stdio: ["ignore", "pipe", "pipe"],
@@ -466,7 +467,7 @@ try {
466
467
  path.join(unsafeProject, LISA_CONFIG_JSON),
467
468
  '{"tracker":"github","harness":"codex"}\n'
468
469
  );
469
- execFileSync("mkfifo", [path.join(unsafeProject, PACKAGE_JSON)]);
470
+ boundedExecFileSync("mkfifo", [path.join(unsafeProject, PACKAGE_JSON)]);
470
471
  const unsafeStarted = Date.now();
471
472
  const unsafe = await health.runDeterministicHealth(unsafeProject, {
472
473
  lisaRoot: root,