@codyswann/lisa 4.4.5 → 4.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +28 -15
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-remote-dispatch/scripts/dispatch.mjs +4 -3
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/providers.mjs +11 -5
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/sync-secret-to-ci.mjs +3 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +36 -23
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +7 -3
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-workstation/scripts/bounded-child.mjs +159 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-workstation/scripts/workstation.mjs +5 -4
- package/plugins/lisa/scripts/design-source-gate.mjs +19 -1
- package/plugins/lisa/scripts/plugin-sync-explain.mjs +23 -1
- package/plugins/lisa/scripts/project-ideation-idempotency-harness.mjs +20 -1
- package/plugins/lisa/skills/lisa-remote-dispatch/scripts/dispatch.mjs +4 -3
- package/plugins/lisa/skills/lisa-secrets-access/scripts/providers.mjs +11 -5
- package/plugins/lisa/skills/lisa-secrets-access/scripts/sync-secret-to-ci.mjs +3 -2
- package/plugins/lisa/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +36 -23
- package/plugins/lisa/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +7 -3
- package/plugins/lisa/skills/lisa-setup-workstation/scripts/bounded-child.mjs +159 -0
- package/plugins/lisa/skills/lisa-setup-workstation/scripts/workstation.mjs +5 -4
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/scripts/design-source-gate.mjs +19 -1
- package/plugins/lisa-agy/scripts/plugin-sync-explain.mjs +23 -1
- package/plugins/lisa-agy/scripts/project-ideation-idempotency-harness.mjs +20 -1
- package/plugins/lisa-agy/skills/lisa-remote-dispatch/scripts/dispatch.mjs +4 -3
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/providers.mjs +11 -5
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/sync-secret-to-ci.mjs +3 -2
- package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +36 -23
- package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +7 -3
- package/plugins/lisa-agy/skills/lisa-setup-workstation/scripts/bounded-child.mjs +159 -0
- package/plugins/lisa-agy/skills/lisa-setup-workstation/scripts/workstation.mjs +5 -4
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/scripts/design-source-gate.mjs +19 -1
- package/plugins/lisa-copilot/scripts/plugin-sync-explain.mjs +23 -1
- package/plugins/lisa-copilot/scripts/project-ideation-idempotency-harness.mjs +20 -1
- package/plugins/lisa-copilot/skills/lisa-remote-dispatch/scripts/dispatch.mjs +4 -3
- package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/providers.mjs +11 -5
- package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/sync-secret-to-ci.mjs +3 -2
- package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +36 -23
- package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +7 -3
- package/plugins/lisa-copilot/skills/lisa-setup-workstation/scripts/bounded-child.mjs +159 -0
- package/plugins/lisa-copilot/skills/lisa-setup-workstation/scripts/workstation.mjs +5 -4
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/scripts/design-source-gate.mjs +19 -1
- package/plugins/lisa-cursor/scripts/plugin-sync-explain.mjs +23 -1
- package/plugins/lisa-cursor/scripts/project-ideation-idempotency-harness.mjs +20 -1
- package/plugins/lisa-cursor/skills/lisa-remote-dispatch/scripts/dispatch.mjs +4 -3
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/providers.mjs +11 -5
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/sync-secret-to-ci.mjs +3 -2
- package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +36 -23
- package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +7 -3
- package/plugins/lisa-cursor/skills/lisa-setup-workstation/scripts/bounded-child.mjs +159 -0
- package/plugins/lisa-cursor/skills/lisa-setup-workstation/scripts/workstation.mjs +5 -4
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/hooks/enforce-config-extensions.mjs +55 -0
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/hooks/enforce-config-extensions.mjs +55 -0
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/hooks/enforce-config-extensions.mjs +55 -0
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/scripts/diff-guard.mjs +3 -2
- package/plugins/lisa-wiki/scripts/ensure-wiki.mjs +3 -2
- package/plugins/lisa-wiki/scripts/ingest-git.mjs +12 -6
- package/plugins/lisa-wiki/scripts/lib/bounded-child.mjs +159 -0
- package/plugins/lisa-wiki/scripts/verify-migration.mjs +3 -2
- package/plugins/lisa-wiki/scripts/verify-wiki-safety.mjs +15 -5
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/scripts/diff-guard.mjs +3 -2
- package/plugins/lisa-wiki-agy/scripts/ensure-wiki.mjs +3 -2
- package/plugins/lisa-wiki-agy/scripts/ingest-git.mjs +12 -6
- package/plugins/lisa-wiki-agy/scripts/lib/bounded-child.mjs +159 -0
- package/plugins/lisa-wiki-agy/scripts/verify-migration.mjs +3 -2
- package/plugins/lisa-wiki-agy/scripts/verify-wiki-safety.mjs +15 -5
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/scripts/diff-guard.mjs +3 -2
- package/plugins/lisa-wiki-copilot/scripts/ensure-wiki.mjs +3 -2
- package/plugins/lisa-wiki-copilot/scripts/ingest-git.mjs +12 -6
- package/plugins/lisa-wiki-copilot/scripts/lib/bounded-child.mjs +159 -0
- package/plugins/lisa-wiki-copilot/scripts/verify-migration.mjs +3 -2
- package/plugins/lisa-wiki-copilot/scripts/verify-wiki-safety.mjs +15 -5
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/scripts/diff-guard.mjs +3 -2
- package/plugins/lisa-wiki-cursor/scripts/ensure-wiki.mjs +3 -2
- package/plugins/lisa-wiki-cursor/scripts/ingest-git.mjs +12 -6
- package/plugins/lisa-wiki-cursor/scripts/lib/bounded-child.mjs +159 -0
- package/plugins/lisa-wiki-cursor/scripts/verify-migration.mjs +3 -2
- package/plugins/lisa-wiki-cursor/scripts/verify-wiki-safety.mjs +15 -5
- package/plugins/src/base/scripts/design-source-gate.mjs +19 -1
- package/plugins/src/base/scripts/plugin-sync-explain.mjs +23 -1
- package/plugins/src/base/scripts/project-ideation-idempotency-harness.mjs +20 -1
- package/plugins/src/base/skills/lisa-remote-dispatch/scripts/dispatch.mjs +4 -3
- package/plugins/src/base/skills/lisa-secrets-access/scripts/providers.mjs +11 -5
- package/plugins/src/base/skills/lisa-secrets-access/scripts/sync-secret-to-ci.mjs +3 -2
- package/plugins/src/base/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +36 -23
- package/plugins/src/base/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +7 -3
- package/plugins/src/base/skills/lisa-setup-workstation/scripts/bounded-child.mjs +159 -0
- package/plugins/src/base/skills/lisa-setup-workstation/scripts/workstation.mjs +5 -4
- package/plugins/src/harper-fabric/hooks/enforce-config-extensions.mjs +55 -0
- package/plugins/src/wiki/scripts/diff-guard.mjs +3 -2
- package/plugins/src/wiki/scripts/ensure-wiki.mjs +3 -2
- package/plugins/src/wiki/scripts/ingest-git.mjs +12 -6
- package/plugins/src/wiki/scripts/lib/bounded-child.mjs +159 -0
- package/plugins/src/wiki/scripts/verify-migration.mjs +3 -2
- package/plugins/src/wiki/scripts/verify-wiki-safety.mjs +15 -5
|
@@ -59,6 +59,44 @@ const gitEnv = () =>
|
|
|
59
59
|
Object.entries(process.env).filter(([key]) => !key.startsWith("GIT_"))
|
|
60
60
|
);
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Milliseconds before the baseline read is presumed hung.
|
|
64
|
+
*
|
|
65
|
+
* A hang detector, not a budget. `git` reached through PATH on macOS goes via
|
|
66
|
+
* Apple's `xcrun` shim, measured over 20 seconds under load against 11ms for a
|
|
67
|
+
* real binary (CodySwannGT/lisa#2887), so a tighter deadline would make this
|
|
68
|
+
* hook's own timeout the ordinary outcome on a busy machine.
|
|
69
|
+
*/
|
|
70
|
+
const GIT_BUDGET_MS = 30_000;
|
|
71
|
+
|
|
72
|
+
/** The baseline could not be read at all, as distinct from "there is none". */
|
|
73
|
+
const UNREADABLE = Symbol("unreadable");
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The config as HEAD holds it: its text, `null` when HEAD has no such file, or
|
|
77
|
+
* `UNREADABLE` when git could not be asked.
|
|
78
|
+
*
|
|
79
|
+
* THE THREE ANSWERS ARE DELIBERATELY THREE. This used to be
|
|
80
|
+
* `result.status === 0 ? result.stdout : null`, and `status === 0` is false for
|
|
81
|
+
* a file that does not exist at HEAD **and** for a child killed at its
|
|
82
|
+
* deadline — so both became `null`, and the caller reads `null` as "no baseline
|
|
83
|
+
* to compare against, allow the edit". A busy machine therefore let through
|
|
84
|
+
* exactly the extension removal this hook exists to block, and nothing anywhere
|
|
85
|
+
* said the word "time".
|
|
86
|
+
*
|
|
87
|
+
* A deadline alone does not fix that. `spawnSync` does not throw when it kills
|
|
88
|
+
* a child; it returns `{ status: null, stdout: "" }`, which takes the same
|
|
89
|
+
* branch it always did. The discrimination has to be written, not scheduled —
|
|
90
|
+
* which is why this call site needed more than the `timeout:` its siblings did.
|
|
91
|
+
*
|
|
92
|
+
* `ETIMEDOUT` is set by Node itself, so this is a platform fact rather than a
|
|
93
|
+
* convention invented here; the shared `bounded-child.mjs` reads the same field,
|
|
94
|
+
* and this hook writes it out inline because a plugin payload materialized as a
|
|
95
|
+
* hook has no module to import from.
|
|
96
|
+
* @param {string} repoRoot Repository root.
|
|
97
|
+
* @returns {string|null|symbol} The text, `null` when absent, `UNREADABLE` when
|
|
98
|
+
* the child was killed.
|
|
99
|
+
*/
|
|
62
100
|
const readGitBlob = repoRoot => {
|
|
63
101
|
const result = spawnSync(
|
|
64
102
|
"git",
|
|
@@ -66,8 +104,11 @@ const readGitBlob = repoRoot => {
|
|
|
66
104
|
{
|
|
67
105
|
encoding: "utf8",
|
|
68
106
|
env: gitEnv(),
|
|
107
|
+
killSignal: "SIGKILL",
|
|
108
|
+
timeout: GIT_BUDGET_MS,
|
|
69
109
|
}
|
|
70
110
|
);
|
|
111
|
+
if (result.error?.code === "ETIMEDOUT") return UNREADABLE;
|
|
71
112
|
return result.status === 0 ? result.stdout : null;
|
|
72
113
|
};
|
|
73
114
|
|
|
@@ -104,6 +145,20 @@ const main = () => {
|
|
|
104
145
|
}
|
|
105
146
|
|
|
106
147
|
const previousText = readGitBlob(repoRoot);
|
|
148
|
+
// FAIL CLOSED on "could not ask", ALLOW on "there is nothing to compare".
|
|
149
|
+
// Those are different findings and only the second is safe to permit: a
|
|
150
|
+
// config with no baseline at HEAD is a new file, while a baseline nobody
|
|
151
|
+
// could read is an unanswered question, and a guard that permits an edit
|
|
152
|
+
// because it was too busy to check has not checked.
|
|
153
|
+
if (previousText === UNREADABLE) {
|
|
154
|
+
process.stderr.write(
|
|
155
|
+
`Blocked: could not read HEAD:${CONFIG_PATH} — git was killed after ` +
|
|
156
|
+
`${String(GIT_BUDGET_MS)}ms without finishing, so the previous ` +
|
|
157
|
+
`extensions are unknown. This is a timeout, not a finding: nothing ` +
|
|
158
|
+
`about the edit was measured. Re-run when the machine is quieter.\n`
|
|
159
|
+
);
|
|
160
|
+
return BLOCKED;
|
|
161
|
+
}
|
|
107
162
|
if (previousText === null) return ALLOWED;
|
|
108
163
|
|
|
109
164
|
const previousExtensions = topLevelExtensionKeys(previousText);
|
|
@@ -59,6 +59,44 @@ const gitEnv = () =>
|
|
|
59
59
|
Object.entries(process.env).filter(([key]) => !key.startsWith("GIT_"))
|
|
60
60
|
);
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Milliseconds before the baseline read is presumed hung.
|
|
64
|
+
*
|
|
65
|
+
* A hang detector, not a budget. `git` reached through PATH on macOS goes via
|
|
66
|
+
* Apple's `xcrun` shim, measured over 20 seconds under load against 11ms for a
|
|
67
|
+
* real binary (CodySwannGT/lisa#2887), so a tighter deadline would make this
|
|
68
|
+
* hook's own timeout the ordinary outcome on a busy machine.
|
|
69
|
+
*/
|
|
70
|
+
const GIT_BUDGET_MS = 30_000;
|
|
71
|
+
|
|
72
|
+
/** The baseline could not be read at all, as distinct from "there is none". */
|
|
73
|
+
const UNREADABLE = Symbol("unreadable");
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The config as HEAD holds it: its text, `null` when HEAD has no such file, or
|
|
77
|
+
* `UNREADABLE` when git could not be asked.
|
|
78
|
+
*
|
|
79
|
+
* THE THREE ANSWERS ARE DELIBERATELY THREE. This used to be
|
|
80
|
+
* `result.status === 0 ? result.stdout : null`, and `status === 0` is false for
|
|
81
|
+
* a file that does not exist at HEAD **and** for a child killed at its
|
|
82
|
+
* deadline — so both became `null`, and the caller reads `null` as "no baseline
|
|
83
|
+
* to compare against, allow the edit". A busy machine therefore let through
|
|
84
|
+
* exactly the extension removal this hook exists to block, and nothing anywhere
|
|
85
|
+
* said the word "time".
|
|
86
|
+
*
|
|
87
|
+
* A deadline alone does not fix that. `spawnSync` does not throw when it kills
|
|
88
|
+
* a child; it returns `{ status: null, stdout: "" }`, which takes the same
|
|
89
|
+
* branch it always did. The discrimination has to be written, not scheduled —
|
|
90
|
+
* which is why this call site needed more than the `timeout:` its siblings did.
|
|
91
|
+
*
|
|
92
|
+
* `ETIMEDOUT` is set by Node itself, so this is a platform fact rather than a
|
|
93
|
+
* convention invented here; the shared `bounded-child.mjs` reads the same field,
|
|
94
|
+
* and this hook writes it out inline because a plugin payload materialized as a
|
|
95
|
+
* hook has no module to import from.
|
|
96
|
+
* @param {string} repoRoot Repository root.
|
|
97
|
+
* @returns {string|null|symbol} The text, `null` when absent, `UNREADABLE` when
|
|
98
|
+
* the child was killed.
|
|
99
|
+
*/
|
|
62
100
|
const readGitBlob = repoRoot => {
|
|
63
101
|
const result = spawnSync(
|
|
64
102
|
"git",
|
|
@@ -66,8 +104,11 @@ const readGitBlob = repoRoot => {
|
|
|
66
104
|
{
|
|
67
105
|
encoding: "utf8",
|
|
68
106
|
env: gitEnv(),
|
|
107
|
+
killSignal: "SIGKILL",
|
|
108
|
+
timeout: GIT_BUDGET_MS,
|
|
69
109
|
}
|
|
70
110
|
);
|
|
111
|
+
if (result.error?.code === "ETIMEDOUT") return UNREADABLE;
|
|
71
112
|
return result.status === 0 ? result.stdout : null;
|
|
72
113
|
};
|
|
73
114
|
|
|
@@ -104,6 +145,20 @@ const main = () => {
|
|
|
104
145
|
}
|
|
105
146
|
|
|
106
147
|
const previousText = readGitBlob(repoRoot);
|
|
148
|
+
// FAIL CLOSED on "could not ask", ALLOW on "there is nothing to compare".
|
|
149
|
+
// Those are different findings and only the second is safe to permit: a
|
|
150
|
+
// config with no baseline at HEAD is a new file, while a baseline nobody
|
|
151
|
+
// could read is an unanswered question, and a guard that permits an edit
|
|
152
|
+
// because it was too busy to check has not checked.
|
|
153
|
+
if (previousText === UNREADABLE) {
|
|
154
|
+
process.stderr.write(
|
|
155
|
+
`Blocked: could not read HEAD:${CONFIG_PATH} — git was killed after ` +
|
|
156
|
+
`${String(GIT_BUDGET_MS)}ms without finishing, so the previous ` +
|
|
157
|
+
`extensions are unknown. This is a timeout, not a finding: nothing ` +
|
|
158
|
+
`about the edit was measured. Re-run when the machine is quieter.\n`
|
|
159
|
+
);
|
|
160
|
+
return BLOCKED;
|
|
161
|
+
}
|
|
107
162
|
if (previousText === null) return ALLOWED;
|
|
108
163
|
|
|
109
164
|
const previousExtensions = topLevelExtensionKeys(previousText);
|
|
@@ -59,6 +59,44 @@ const gitEnv = () =>
|
|
|
59
59
|
Object.entries(process.env).filter(([key]) => !key.startsWith("GIT_"))
|
|
60
60
|
);
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Milliseconds before the baseline read is presumed hung.
|
|
64
|
+
*
|
|
65
|
+
* A hang detector, not a budget. `git` reached through PATH on macOS goes via
|
|
66
|
+
* Apple's `xcrun` shim, measured over 20 seconds under load against 11ms for a
|
|
67
|
+
* real binary (CodySwannGT/lisa#2887), so a tighter deadline would make this
|
|
68
|
+
* hook's own timeout the ordinary outcome on a busy machine.
|
|
69
|
+
*/
|
|
70
|
+
const GIT_BUDGET_MS = 30_000;
|
|
71
|
+
|
|
72
|
+
/** The baseline could not be read at all, as distinct from "there is none". */
|
|
73
|
+
const UNREADABLE = Symbol("unreadable");
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The config as HEAD holds it: its text, `null` when HEAD has no such file, or
|
|
77
|
+
* `UNREADABLE` when git could not be asked.
|
|
78
|
+
*
|
|
79
|
+
* THE THREE ANSWERS ARE DELIBERATELY THREE. This used to be
|
|
80
|
+
* `result.status === 0 ? result.stdout : null`, and `status === 0` is false for
|
|
81
|
+
* a file that does not exist at HEAD **and** for a child killed at its
|
|
82
|
+
* deadline — so both became `null`, and the caller reads `null` as "no baseline
|
|
83
|
+
* to compare against, allow the edit". A busy machine therefore let through
|
|
84
|
+
* exactly the extension removal this hook exists to block, and nothing anywhere
|
|
85
|
+
* said the word "time".
|
|
86
|
+
*
|
|
87
|
+
* A deadline alone does not fix that. `spawnSync` does not throw when it kills
|
|
88
|
+
* a child; it returns `{ status: null, stdout: "" }`, which takes the same
|
|
89
|
+
* branch it always did. The discrimination has to be written, not scheduled —
|
|
90
|
+
* which is why this call site needed more than the `timeout:` its siblings did.
|
|
91
|
+
*
|
|
92
|
+
* `ETIMEDOUT` is set by Node itself, so this is a platform fact rather than a
|
|
93
|
+
* convention invented here; the shared `bounded-child.mjs` reads the same field,
|
|
94
|
+
* and this hook writes it out inline because a plugin payload materialized as a
|
|
95
|
+
* hook has no module to import from.
|
|
96
|
+
* @param {string} repoRoot Repository root.
|
|
97
|
+
* @returns {string|null|symbol} The text, `null` when absent, `UNREADABLE` when
|
|
98
|
+
* the child was killed.
|
|
99
|
+
*/
|
|
62
100
|
const readGitBlob = repoRoot => {
|
|
63
101
|
const result = spawnSync(
|
|
64
102
|
"git",
|
|
@@ -66,8 +104,11 @@ const readGitBlob = repoRoot => {
|
|
|
66
104
|
{
|
|
67
105
|
encoding: "utf8",
|
|
68
106
|
env: gitEnv(),
|
|
107
|
+
killSignal: "SIGKILL",
|
|
108
|
+
timeout: GIT_BUDGET_MS,
|
|
69
109
|
}
|
|
70
110
|
);
|
|
111
|
+
if (result.error?.code === "ETIMEDOUT") return UNREADABLE;
|
|
71
112
|
return result.status === 0 ? result.stdout : null;
|
|
72
113
|
};
|
|
73
114
|
|
|
@@ -104,6 +145,20 @@ const main = () => {
|
|
|
104
145
|
}
|
|
105
146
|
|
|
106
147
|
const previousText = readGitBlob(repoRoot);
|
|
148
|
+
// FAIL CLOSED on "could not ask", ALLOW on "there is nothing to compare".
|
|
149
|
+
// Those are different findings and only the second is safe to permit: a
|
|
150
|
+
// config with no baseline at HEAD is a new file, while a baseline nobody
|
|
151
|
+
// could read is an unanswered question, and a guard that permits an edit
|
|
152
|
+
// because it was too busy to check has not checked.
|
|
153
|
+
if (previousText === UNREADABLE) {
|
|
154
|
+
process.stderr.write(
|
|
155
|
+
`Blocked: could not read HEAD:${CONFIG_PATH} — git was killed after ` +
|
|
156
|
+
`${String(GIT_BUDGET_MS)}ms without finishing, so the previous ` +
|
|
157
|
+
`extensions are unknown. This is a timeout, not a finding: nothing ` +
|
|
158
|
+
`about the edit was measured. Re-run when the machine is quieter.\n`
|
|
159
|
+
);
|
|
160
|
+
return BLOCKED;
|
|
161
|
+
}
|
|
107
162
|
if (previousText === null) return ALLOWED;
|
|
108
163
|
|
|
109
164
|
const previousExtensions = topLevelExtensionKeys(previousText);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "4.4.
|
|
3
|
+
"version": "4.4.7",
|
|
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.
|
|
3
|
+
"version": "4.4.7",
|
|
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.
|
|
3
|
+
"version": "4.4.7",
|
|
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.
|
|
3
|
+
"version": "4.4.7",
|
|
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.
|
|
3
|
+
"version": "4.4.7",
|
|
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"
|
|
@@ -16,9 +16,10 @@
|
|
|
16
16
|
* Usage: node diff-guard.mjs --allow <glob> [--allow <glob>...] [--base <ref>]
|
|
17
17
|
* Exit 0 = all changes within allowed globs, 1 = out-of-bounds change (or git error).
|
|
18
18
|
*/
|
|
19
|
-
import { execFileSync } from "node:child_process";
|
|
20
19
|
import { globToRegExp } from "./_wiki-lib.mjs";
|
|
21
20
|
|
|
21
|
+
import { boundedChildOutput } from "./lib/bounded-child.mjs";
|
|
22
|
+
|
|
22
23
|
function fail(msg) {
|
|
23
24
|
console.error(`✗ ${msg}`);
|
|
24
25
|
process.exit(1);
|
|
@@ -51,7 +52,7 @@ if (allows.length === 0) {
|
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
function git(args) {
|
|
54
|
-
return
|
|
55
|
+
return boundedChildOutput("git", args, { encoding: "utf8" });
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
let repoRoot;
|
|
@@ -49,7 +49,8 @@
|
|
|
49
49
|
*/
|
|
50
50
|
import fs from "node:fs";
|
|
51
51
|
import path from "node:path";
|
|
52
|
-
|
|
52
|
+
|
|
53
|
+
import { boundedChildOutput } from "./lib/bounded-child.mjs";
|
|
53
54
|
|
|
54
55
|
const GITIGNORE_BEGIN = "# BEGIN: AI GUARDRAILS WIKI MIRROR";
|
|
55
56
|
const GITIGNORE_END = "# END: AI GUARDRAILS WIKI MIRROR";
|
|
@@ -71,7 +72,7 @@ function readJsonSafe(file) {
|
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
74
|
function git(args, cwd) {
|
|
74
|
-
return
|
|
75
|
+
return boundedChildOutput("git", args, {
|
|
75
76
|
cwd,
|
|
76
77
|
stdio: ["ignore", "pipe", "pipe"],
|
|
77
78
|
encoding: "utf8",
|
|
@@ -14,9 +14,13 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import fs from "node:fs";
|
|
16
16
|
import path from "node:path";
|
|
17
|
-
import { execFileSync } from "node:child_process";
|
|
18
17
|
import { readJsonSafe, writeSanitizedSourceNote } from "./_wiki-lib.mjs";
|
|
19
18
|
|
|
19
|
+
import {
|
|
20
|
+
boundedChildOutput,
|
|
21
|
+
rethrowIfChildTimeout,
|
|
22
|
+
} from "./lib/bounded-child.mjs";
|
|
23
|
+
|
|
20
24
|
const argv = process.argv.slice(2);
|
|
21
25
|
const opt = (n, d) => {
|
|
22
26
|
const i = argv.indexOf(n);
|
|
@@ -35,7 +39,7 @@ const fail = m => {
|
|
|
35
39
|
process.exit(1);
|
|
36
40
|
};
|
|
37
41
|
const git = args =>
|
|
38
|
-
|
|
42
|
+
boundedChildOutput("git", ["-C", repo, ...args], { encoding: "utf8" }).trim();
|
|
39
43
|
const tryGit = args => {
|
|
40
44
|
try {
|
|
41
45
|
return git(args);
|
|
@@ -45,11 +49,13 @@ const tryGit = args => {
|
|
|
45
49
|
};
|
|
46
50
|
const commitExists = c => {
|
|
47
51
|
try {
|
|
48
|
-
|
|
52
|
+
boundedChildOutput("git", ["-C", repo, "cat-file", "-e", `${c}^{commit}`], {
|
|
49
53
|
stdio: "ignore",
|
|
50
54
|
});
|
|
51
55
|
return true;
|
|
52
|
-
} catch {
|
|
56
|
+
} catch (error) {
|
|
57
|
+
// `false` is a verdict of "absent", not an admission of ignorance.
|
|
58
|
+
rethrowIfChildTimeout(error);
|
|
53
59
|
return false;
|
|
54
60
|
}
|
|
55
61
|
};
|
|
@@ -87,7 +93,7 @@ let prSummary = "(no GitHub repo resolved — PR history skipped)";
|
|
|
87
93
|
let ghRepo = githubRepo;
|
|
88
94
|
if (!ghRepo) {
|
|
89
95
|
try {
|
|
90
|
-
ghRepo =
|
|
96
|
+
ghRepo = boundedChildOutput(
|
|
91
97
|
"gh",
|
|
92
98
|
["repo", "view", "--json", "nameWithOwner", "-q", ".nameWithOwner"],
|
|
93
99
|
{ cwd: repo, encoding: "utf8" }
|
|
@@ -99,7 +105,7 @@ if (!ghRepo) {
|
|
|
99
105
|
if (ghRepo) {
|
|
100
106
|
try {
|
|
101
107
|
const prs = JSON.parse(
|
|
102
|
-
|
|
108
|
+
boundedChildOutput(
|
|
103
109
|
"gh",
|
|
104
110
|
[
|
|
105
111
|
"pr",
|