@codyswann/lisa 1.95.0 → 1.96.0
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/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +41 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/codex/agent-installer.d.ts +56 -0
- package/dist/codex/agent-installer.d.ts.map +1 -0
- package/dist/codex/agent-installer.js +201 -0
- package/dist/codex/agent-installer.js.map +1 -0
- package/dist/codex/agent-transformer.d.ts +53 -0
- package/dist/codex/agent-transformer.d.ts.map +1 -0
- package/dist/codex/agent-transformer.js +181 -0
- package/dist/codex/agent-transformer.js.map +1 -0
- package/dist/codex/agents-md-installer.d.ts +24 -0
- package/dist/codex/agents-md-installer.d.ts.map +1 -0
- package/dist/codex/agents-md-installer.js +63 -0
- package/dist/codex/agents-md-installer.js.map +1 -0
- package/dist/codex/hooks-installer.d.ts +24 -0
- package/dist/codex/hooks-installer.d.ts.map +1 -0
- package/dist/codex/hooks-installer.js +206 -0
- package/dist/codex/hooks-installer.js.map +1 -0
- package/dist/codex/hooks-merger.d.ts +82 -0
- package/dist/codex/hooks-merger.d.ts.map +1 -0
- package/dist/codex/hooks-merger.js +127 -0
- package/dist/codex/hooks-merger.js.map +1 -0
- package/dist/codex/manifest.d.ts +32 -0
- package/dist/codex/manifest.d.ts.map +1 -0
- package/dist/codex/manifest.js +86 -0
- package/dist/codex/manifest.js.map +1 -0
- package/dist/codex/settings-installer.d.ts +48 -0
- package/dist/codex/settings-installer.d.ts.map +1 -0
- package/dist/codex/settings-installer.js +276 -0
- package/dist/codex/settings-installer.js.map +1 -0
- package/dist/codex/skills-installer.d.ts +46 -0
- package/dist/codex/skills-installer.d.ts.map +1 -0
- package/dist/codex/skills-installer.js +344 -0
- package/dist/codex/skills-installer.js.map +1 -0
- package/dist/core/config.d.ts +19 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +13 -0
- package/dist/core/config.js.map +1 -1
- package/dist/core/lisa.d.ts +12 -0
- package/dist/core/lisa.d.ts.map +1 -1
- package/dist/core/lisa.js +48 -0
- package/dist/core/lisa.js.map +1 -1
- package/dist/core/project-config.d.ts +49 -0
- package/dist/core/project-config.d.ts.map +1 -0
- package/dist/core/project-config.js +119 -0
- package/dist/core/project-config.js.map +1 -0
- package/package.json +3 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/agents/jira-build-intake.md +58 -0
- package/plugins/lisa/agents/notion-prd-intake.md +57 -0
- package/plugins/lisa/commands/jira/build-intake.md +7 -0
- package/plugins/lisa/commands/jira/source-artifacts.md +6 -0
- package/plugins/lisa/commands/jira/validate-ticket.md +7 -0
- package/plugins/lisa/commands/notion-prd-intake.md +7 -0
- package/plugins/lisa/commands/prd-ticket-coverage.md +7 -0
- package/plugins/lisa/commands/product-walkthrough.md +7 -0
- package/plugins/lisa/skills/jira-build-intake/SKILL.md +134 -0
- package/plugins/lisa/skills/jira-create/SKILL.md +53 -30
- package/plugins/lisa/skills/jira-source-artifacts/SKILL.md +107 -0
- package/plugins/lisa/skills/jira-validate-ticket/SKILL.md +224 -0
- package/plugins/lisa/skills/jira-verify/SKILL.md +15 -91
- package/plugins/lisa/skills/jira-write-ticket/SKILL.md +20 -15
- package/plugins/lisa/skills/notion-prd-intake/SKILL.md +169 -0
- package/plugins/lisa/skills/notion-to-jira/SKILL.md +137 -95
- package/plugins/lisa/skills/prd-ticket-coverage/SKILL.md +137 -0
- package/plugins/lisa/skills/product-walkthrough/SKILL.md +129 -0
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/skills/jira-create/SKILL.md +60 -28
- package/plugins/lisa-expo/skills/jira-verify/SKILL.md +14 -34
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/skills/jira-create/SKILL.md +59 -28
- package/plugins/lisa-rails/skills/jira-verify/SKILL.md +13 -16
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/agents/jira-build-intake.md +58 -0
- package/plugins/src/base/agents/notion-prd-intake.md +57 -0
- package/plugins/src/base/commands/jira/build-intake.md +7 -0
- package/plugins/src/base/commands/jira/source-artifacts.md +6 -0
- package/plugins/src/base/commands/jira/validate-ticket.md +7 -0
- package/plugins/src/base/commands/notion-prd-intake.md +7 -0
- package/plugins/src/base/commands/prd-ticket-coverage.md +7 -0
- package/plugins/src/base/commands/product-walkthrough.md +7 -0
- package/plugins/src/base/skills/jira-build-intake/SKILL.md +134 -0
- package/plugins/src/base/skills/jira-create/SKILL.md +53 -30
- package/plugins/src/base/skills/jira-source-artifacts/SKILL.md +107 -0
- package/plugins/src/base/skills/jira-validate-ticket/SKILL.md +224 -0
- package/plugins/src/base/skills/jira-verify/SKILL.md +15 -91
- package/plugins/src/base/skills/jira-write-ticket/SKILL.md +20 -15
- package/plugins/src/base/skills/notion-prd-intake/SKILL.md +169 -0
- package/plugins/src/base/skills/notion-to-jira/SKILL.md +137 -95
- package/plugins/src/base/skills/prd-ticket-coverage/SKILL.md +137 -0
- package/plugins/src/base/skills/product-walkthrough/SKILL.md +129 -0
- package/plugins/src/expo/skills/jira-create/SKILL.md +60 -28
- package/plugins/src/expo/skills/jira-verify/SKILL.md +14 -34
- package/plugins/src/rails/skills/jira-create/SKILL.md +59 -28
- package/plugins/src/rails/skills/jira-verify/SKILL.md +13 -16
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Filename of the Codex project doc at the host project root */
|
|
2
|
+
export declare const AGENTS_MD_FILENAME = "AGENTS.md";
|
|
3
|
+
/** Result of the AGENTS.md install pass */
|
|
4
|
+
export interface AgentsMdInstallResult {
|
|
5
|
+
/** True if Lisa created the file (false if it already existed) */
|
|
6
|
+
readonly created: boolean;
|
|
7
|
+
/** Path written, relative to the host project root (or empty if no-op) */
|
|
8
|
+
readonly relativePath: string | undefined;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Write a starter AGENTS.md template at the host project root, but only if
|
|
12
|
+
* the file doesn't already exist (create-only semantics).
|
|
13
|
+
* @param destDir - Absolute path to the host project root
|
|
14
|
+
* @returns Result describing whether a file was created
|
|
15
|
+
*/
|
|
16
|
+
export declare function installAgentsMd(destDir: string): Promise<AgentsMdInstallResult>;
|
|
17
|
+
/**
|
|
18
|
+
* Starter template content. Intentionally short — the heavy lifting (Lisa
|
|
19
|
+
* rules, intent routing, orchestration) lives in `.codex/lisa-rules/` and
|
|
20
|
+
* is injected via the SessionStart hook, so AGENTS.md is reserved for
|
|
21
|
+
* host-specific notes.
|
|
22
|
+
*/
|
|
23
|
+
export declare const AGENTS_MD_TEMPLATE = "# Project Guidance for Codex\n\nThis project uses Lisa governance. Codex sessions automatically receive\nLisa's rules, agents, and skills via the SessionStart hook in\n`.codex/hooks/lisa/inject-rules.sh`.\n\nThis file is for **project-specific guidance** \u2014 add anything Codex should\nknow about *this particular* project that isn't covered by Lisa's rules.\n\n## What lives where\n\n- `.codex/agents/lisa/` \u2014 Lisa-managed subagent role definitions\n- `.codex/skills/lisa/` \u2014 Lisa-managed skills (invoke via `$<name>`)\n- `.codex/lisa-rules/` \u2014 Lisa rules content (injected at session start)\n- `.codex/hooks/lisa/` \u2014 Lisa-managed hook scripts\n- `.codex/config.toml` \u2014 partly Lisa-managed Codex config\n\n## Custom rules for this project\n\nAdd project-specific guidance below. This section is preserved across Lisa\nupdates (`AGENTS.md` is create-only).\n";
|
|
24
|
+
//# sourceMappingURL=agents-md-installer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agents-md-installer.d.ts","sourceRoot":"","sources":["../../src/codex/agents-md-installer.ts"],"names":[],"mappings":"AAmBA,iEAAiE;AACjE,eAAO,MAAM,kBAAkB,cAAc,CAAC;AAE9C,2CAA2C;AAC3C,MAAM,WAAW,qBAAqB;IACpC,kEAAkE;IAClE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,0EAA0E;IAC1E,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3C;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,qBAAqB,CAAC,CAOhC;AAED;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,23BAqB9B,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Emit a create-only AGENTS.md template into the host project root.
|
|
3
|
+
*
|
|
4
|
+
* Codex auto-loads `AGENTS.md` from the project tree on every session
|
|
5
|
+
* (per `developers.openai.com/codex/guides/agents-md`). Lisa ships a
|
|
6
|
+
* starter template so hosts know:
|
|
7
|
+
* - Lisa governance is active in this project (rules injected via
|
|
8
|
+
* SessionStart hook from `.codex/lisa-rules/`)
|
|
9
|
+
* - This file is the place to add project-specific guidance
|
|
10
|
+
*
|
|
11
|
+
* Create-only: never overwritten on subsequent `lisa` runs. The host owns
|
|
12
|
+
* the file once it exists. (Lisa's actual rules go via the inject-rules
|
|
13
|
+
* hook, so AGENTS.md is purely a host-facing knob.)
|
|
14
|
+
* @module codex/agents-md-installer
|
|
15
|
+
*/
|
|
16
|
+
import * as fse from "fs-extra";
|
|
17
|
+
import { writeFile } from "node:fs/promises";
|
|
18
|
+
import * as path from "node:path";
|
|
19
|
+
/** Filename of the Codex project doc at the host project root */
|
|
20
|
+
export const AGENTS_MD_FILENAME = "AGENTS.md";
|
|
21
|
+
/**
|
|
22
|
+
* Write a starter AGENTS.md template at the host project root, but only if
|
|
23
|
+
* the file doesn't already exist (create-only semantics).
|
|
24
|
+
* @param destDir - Absolute path to the host project root
|
|
25
|
+
* @returns Result describing whether a file was created
|
|
26
|
+
*/
|
|
27
|
+
export async function installAgentsMd(destDir) {
|
|
28
|
+
const filePath = path.join(destDir, AGENTS_MD_FILENAME);
|
|
29
|
+
if (await fse.pathExists(filePath)) {
|
|
30
|
+
return { created: false, relativePath: undefined };
|
|
31
|
+
}
|
|
32
|
+
await writeFile(filePath, AGENTS_MD_TEMPLATE, "utf8");
|
|
33
|
+
return { created: true, relativePath: AGENTS_MD_FILENAME };
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Starter template content. Intentionally short — the heavy lifting (Lisa
|
|
37
|
+
* rules, intent routing, orchestration) lives in `.codex/lisa-rules/` and
|
|
38
|
+
* is injected via the SessionStart hook, so AGENTS.md is reserved for
|
|
39
|
+
* host-specific notes.
|
|
40
|
+
*/
|
|
41
|
+
export const AGENTS_MD_TEMPLATE = `# Project Guidance for Codex
|
|
42
|
+
|
|
43
|
+
This project uses Lisa governance. Codex sessions automatically receive
|
|
44
|
+
Lisa's rules, agents, and skills via the SessionStart hook in
|
|
45
|
+
\`.codex/hooks/lisa/inject-rules.sh\`.
|
|
46
|
+
|
|
47
|
+
This file is for **project-specific guidance** — add anything Codex should
|
|
48
|
+
know about *this particular* project that isn't covered by Lisa's rules.
|
|
49
|
+
|
|
50
|
+
## What lives where
|
|
51
|
+
|
|
52
|
+
- \`.codex/agents/lisa/\` — Lisa-managed subagent role definitions
|
|
53
|
+
- \`.codex/skills/lisa/\` — Lisa-managed skills (invoke via \`$<name>\`)
|
|
54
|
+
- \`.codex/lisa-rules/\` — Lisa rules content (injected at session start)
|
|
55
|
+
- \`.codex/hooks/lisa/\` — Lisa-managed hook scripts
|
|
56
|
+
- \`.codex/config.toml\` — partly Lisa-managed Codex config
|
|
57
|
+
|
|
58
|
+
## Custom rules for this project
|
|
59
|
+
|
|
60
|
+
Add project-specific guidance below. This section is preserved across Lisa
|
|
61
|
+
updates (\`AGENTS.md\` is create-only).
|
|
62
|
+
`;
|
|
63
|
+
//# sourceMappingURL=agents-md-installer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agents-md-installer.js","sourceRoot":"","sources":["../../src/codex/agents-md-installer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAChC,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,iEAAiE;AACjE,MAAM,CAAC,MAAM,kBAAkB,GAAG,WAAW,CAAC;AAU9C;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAAe;IAEf,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,kBAAkB,CAAC,CAAC;IACxD,IAAI,MAAM,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACnC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC;IACrD,CAAC;IACD,MAAM,SAAS,CAAC,QAAQ,EAAE,kBAAkB,EAAE,MAAM,CAAC,CAAC;IACtD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,kBAAkB,EAAE,CAAC;AAC7D,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;CAqBjC,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ProjectType } from "../core/config.js";
|
|
2
|
+
/** Subdirectory inside `.codex/` for Lisa-managed hook scripts */
|
|
3
|
+
export declare const LISA_HOOKS_SUBDIR: string;
|
|
4
|
+
/** Subdirectory inside `.codex/` for Lisa rules content (read by inject-rules) */
|
|
5
|
+
export declare const LISA_RULES_SUBDIR = "lisa-rules";
|
|
6
|
+
/** Filename of the Codex hooks config file inside `.codex/` */
|
|
7
|
+
export declare const HOOKS_FILENAME = "hooks.json";
|
|
8
|
+
/** Result of the hooks install pass */
|
|
9
|
+
export interface HooksInstallResult {
|
|
10
|
+
/** Files written, relative to `.codex/`. Used to update the manifest. */
|
|
11
|
+
readonly managedFiles: readonly string[];
|
|
12
|
+
/** Number of Lisa hook entries written into hooks.json */
|
|
13
|
+
readonly hookEntries: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Install Lisa hook scripts + rules + hooks.json entries.
|
|
17
|
+
* @param lisaDir - Absolute path to the Lisa repo root (or installed package)
|
|
18
|
+
* @param destDir - Absolute path to the host project root
|
|
19
|
+
* @param detectedTypes - Project types Lisa detected; used to filter stack-
|
|
20
|
+
* specific hooks. Always includes the universal hooks regardless.
|
|
21
|
+
* @returns Result describing what was written
|
|
22
|
+
*/
|
|
23
|
+
export declare function installHooks(lisaDir: string, destDir: string, detectedTypes: readonly ProjectType[]): Promise<HooksInstallResult>;
|
|
24
|
+
//# sourceMappingURL=hooks-installer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks-installer.d.ts","sourceRoot":"","sources":["../../src/codex/hooks-installer.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAUrD,kEAAkE;AAClE,eAAO,MAAM,iBAAiB,QAA6B,CAAC;AAE5D,kFAAkF;AAClF,eAAO,MAAM,iBAAiB,eAAe,CAAC;AAE9C,+DAA+D;AAC/D,eAAO,MAAM,cAAc,eAAe,CAAC;AAuF3C,uCAAuC;AACvC,MAAM,WAAW,kBAAkB;IACjC,yEAAyE;IACzE,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,0DAA0D;IAC1D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,aAAa,EAAE,SAAS,WAAW,EAAE,GACpC,OAAO,CAAC,kBAAkB,CAAC,CA2C7B"}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Install Lisa-managed Codex hooks into a host project.
|
|
3
|
+
*
|
|
4
|
+
* Pipeline:
|
|
5
|
+
* 1. Filter hook catalog by detected project types
|
|
6
|
+
* 2. Copy each script from `src/codex/scripts/` → `.codex/hooks/lisa/`
|
|
7
|
+
* 3. For inject-rules: also mirror Lisa rules into `.codex/lisa-rules/`
|
|
8
|
+
* 4. Tagged-merge `.codex/hooks.json`
|
|
9
|
+
*
|
|
10
|
+
* Codex hook event support map (vs. Lisa's existing Claude Code hooks):
|
|
11
|
+
* - SessionStart, PreToolUse, PostToolUse, UserPromptSubmit, Stop ✅
|
|
12
|
+
* - PermissionRequest ✅ (Codex-only)
|
|
13
|
+
* - SubagentStart, SessionEnd, Notification, PreCompact ❌ (Codex doesn't have these)
|
|
14
|
+
* @module codex/hooks-installer
|
|
15
|
+
*/
|
|
16
|
+
import * as fse from "fs-extra";
|
|
17
|
+
import { chmod, copyFile, readFile, readdir, writeFile, } from "node:fs/promises";
|
|
18
|
+
import * as path from "node:path";
|
|
19
|
+
import { fileURLToPath } from "node:url";
|
|
20
|
+
import { mergeLisaHooks, parseHooksFile, serializeHooksFile, } from "./hooks-merger.js";
|
|
21
|
+
/** Subdirectory inside `.codex/` for Lisa-managed hook scripts */
|
|
22
|
+
export const LISA_HOOKS_SUBDIR = path.join("hooks", "lisa");
|
|
23
|
+
/** Subdirectory inside `.codex/` for Lisa rules content (read by inject-rules) */
|
|
24
|
+
export const LISA_RULES_SUBDIR = "lisa-rules";
|
|
25
|
+
/** Filename of the Codex hooks config file inside `.codex/` */
|
|
26
|
+
export const HOOKS_FILENAME = "hooks.json";
|
|
27
|
+
/**
|
|
28
|
+
* Matcher regex shared by every PostToolUse/PreToolUse hook that fires on
|
|
29
|
+
* a file write. Codex emits these tool names for filesystem edits across
|
|
30
|
+
* its three write paths (text Edit, file Write, apply_patch diff).
|
|
31
|
+
*/
|
|
32
|
+
const WRITE_MATCHER = "Edit|Write|apply_patch";
|
|
33
|
+
/**
|
|
34
|
+
* Hook catalog. Adding a new hook? Three steps:
|
|
35
|
+
* 1. Drop the script into `src/codex/scripts/<filename>`
|
|
36
|
+
* 2. Add an entry here
|
|
37
|
+
* 3. Add tests in `tests/unit/codex/hooks-installer.test.ts`
|
|
38
|
+
*
|
|
39
|
+
* Stack-specific hooks are gated by `forProjectTypes` so they're only shipped
|
|
40
|
+
* when the relevant project type is detected.
|
|
41
|
+
*/
|
|
42
|
+
const HOOK_CATALOG = [
|
|
43
|
+
{
|
|
44
|
+
id: "inject-rules",
|
|
45
|
+
event: "SessionStart",
|
|
46
|
+
matcher: "",
|
|
47
|
+
scriptFilename: "inject-rules.sh",
|
|
48
|
+
forProjectTypes: ["*"],
|
|
49
|
+
statusMessage: "Injecting Lisa rules into session context",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: "notify-ntfy",
|
|
53
|
+
event: "Stop",
|
|
54
|
+
matcher: "",
|
|
55
|
+
scriptFilename: "notify-ntfy.sh",
|
|
56
|
+
forProjectTypes: ["*"],
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "format-on-edit",
|
|
60
|
+
event: "PostToolUse",
|
|
61
|
+
matcher: WRITE_MATCHER,
|
|
62
|
+
scriptFilename: "format-on-edit.sh",
|
|
63
|
+
forProjectTypes: ["typescript"],
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: "lint-on-edit",
|
|
67
|
+
event: "PostToolUse",
|
|
68
|
+
matcher: WRITE_MATCHER,
|
|
69
|
+
scriptFilename: "lint-on-edit.sh",
|
|
70
|
+
forProjectTypes: ["typescript"],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: "sg-scan-on-edit",
|
|
74
|
+
event: "PostToolUse",
|
|
75
|
+
matcher: WRITE_MATCHER,
|
|
76
|
+
scriptFilename: "sg-scan-on-edit.sh",
|
|
77
|
+
forProjectTypes: ["typescript"],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: "rubocop-on-edit",
|
|
81
|
+
event: "PostToolUse",
|
|
82
|
+
matcher: WRITE_MATCHER,
|
|
83
|
+
scriptFilename: "rubocop-on-edit.sh",
|
|
84
|
+
forProjectTypes: ["rails"],
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: "block-migration-edits",
|
|
88
|
+
event: "PreToolUse",
|
|
89
|
+
matcher: WRITE_MATCHER,
|
|
90
|
+
scriptFilename: "block-migration-edits.sh",
|
|
91
|
+
forProjectTypes: ["nestjs"],
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
/**
|
|
95
|
+
* Install Lisa hook scripts + rules + hooks.json entries.
|
|
96
|
+
* @param lisaDir - Absolute path to the Lisa repo root (or installed package)
|
|
97
|
+
* @param destDir - Absolute path to the host project root
|
|
98
|
+
* @param detectedTypes - Project types Lisa detected; used to filter stack-
|
|
99
|
+
* specific hooks. Always includes the universal hooks regardless.
|
|
100
|
+
* @returns Result describing what was written
|
|
101
|
+
*/
|
|
102
|
+
export async function installHooks(lisaDir, destDir, detectedTypes) {
|
|
103
|
+
const codexDir = path.join(destDir, ".codex");
|
|
104
|
+
const hooksDir = path.join(codexDir, LISA_HOOKS_SUBDIR);
|
|
105
|
+
const rulesDir = path.join(codexDir, LISA_RULES_SUBDIR);
|
|
106
|
+
await fse.ensureDir(hooksDir);
|
|
107
|
+
await fse.ensureDir(rulesDir);
|
|
108
|
+
const applicable = filterCatalogByTypes(detectedTypes);
|
|
109
|
+
// Step 1: copy every applicable script and collect their relative paths
|
|
110
|
+
const scriptFiles = await Promise.all(applicable.map(async (entry) => {
|
|
111
|
+
const scriptSource = resolveBundledScript(entry.scriptFilename);
|
|
112
|
+
const scriptDest = path.join(hooksDir, entry.scriptFilename);
|
|
113
|
+
await copyFile(scriptSource, scriptDest);
|
|
114
|
+
await chmod(scriptDest, 0o755);
|
|
115
|
+
return path.join(LISA_HOOKS_SUBDIR, entry.scriptFilename);
|
|
116
|
+
}));
|
|
117
|
+
// Step 2: mirror rules from Lisa into .codex/lisa-rules/ (only when
|
|
118
|
+
// inject-rules is being installed — i.e., always, since it's a "*" hook)
|
|
119
|
+
const ruleFiles = applicable.some(e => e.id === "inject-rules")
|
|
120
|
+
? (await mirrorRules(lisaDir, rulesDir)).map(file => path.join(LISA_RULES_SUBDIR, file))
|
|
121
|
+
: [];
|
|
122
|
+
// Step 3: tagged-merge hooks.json
|
|
123
|
+
const hooksFilePath = path.join(codexDir, HOOKS_FILENAME);
|
|
124
|
+
const existing = await readHooksFile(hooksFilePath);
|
|
125
|
+
const lisaHookSpecs = applicable.map(entry => catalogEntryToSpec(entry, destDir));
|
|
126
|
+
const merged = mergeLisaHooks(existing, lisaHookSpecs);
|
|
127
|
+
await writeFile(hooksFilePath, serializeHooksFile(merged), "utf8");
|
|
128
|
+
return {
|
|
129
|
+
managedFiles: Object.freeze([...scriptFiles, ...ruleFiles, HOOKS_FILENAME]),
|
|
130
|
+
hookEntries: lisaHookSpecs.length,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Filter the catalog by detected project types. Universal hooks (`"*"`)
|
|
135
|
+
* always pass; stack-specific hooks pass only if their type is detected.
|
|
136
|
+
* @param detectedTypes - Project types Lisa detected for the host
|
|
137
|
+
* @returns The catalog entries that apply to this host
|
|
138
|
+
*/
|
|
139
|
+
function filterCatalogByTypes(detectedTypes) {
|
|
140
|
+
const detectedSet = new Set(detectedTypes);
|
|
141
|
+
return HOOK_CATALOG.filter(entry => entry.forProjectTypes.some(t => t === "*" || detectedSet.has(t)));
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Convert a catalog entry into the LisaHookSpec the merger consumes.
|
|
145
|
+
*
|
|
146
|
+
* The script path resolves at hook-firing time via
|
|
147
|
+
* `git rev-parse --show-toplevel` because Codex sets the hook script's cwd
|
|
148
|
+
* to the session cwd, not the repo root. Falls back to `pwd` if not in a git
|
|
149
|
+
* repo (rare).
|
|
150
|
+
* @param entry - One catalog entry to translate
|
|
151
|
+
* @param _destDir - Reserved for future per-host customization; currently unused
|
|
152
|
+
* @returns A LisaHookSpec ready to feed into mergeLisaHooks
|
|
153
|
+
*/
|
|
154
|
+
function catalogEntryToSpec(entry, _destDir) {
|
|
155
|
+
const command = `bash "$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.codex/${LISA_HOOKS_SUBDIR}/${entry.scriptFilename}"`;
|
|
156
|
+
return {
|
|
157
|
+
id: entry.id,
|
|
158
|
+
event: entry.event,
|
|
159
|
+
matcher: entry.matcher,
|
|
160
|
+
command,
|
|
161
|
+
...(entry.statusMessage !== undefined
|
|
162
|
+
? { statusMessage: entry.statusMessage }
|
|
163
|
+
: {}),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Read an existing hooks.json file, returning {} if absent.
|
|
168
|
+
* @param hooksFilePath - Absolute path to `<destDir>/.codex/hooks.json`
|
|
169
|
+
* @returns Parsed HooksFile (empty object if the file doesn't exist)
|
|
170
|
+
*/
|
|
171
|
+
async function readHooksFile(hooksFilePath) {
|
|
172
|
+
if (!(await fse.pathExists(hooksFilePath))) {
|
|
173
|
+
return {};
|
|
174
|
+
}
|
|
175
|
+
const raw = await readFile(hooksFilePath, "utf8");
|
|
176
|
+
return parseHooksFile(raw);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Resolve a bundled script path. Scripts ship inside the Lisa source tree at
|
|
180
|
+
* `src/codex/scripts/<name>` and are accessible at runtime by computing the
|
|
181
|
+
* path relative to this module's URL.
|
|
182
|
+
* @param filename - Script filename (e.g. "inject-rules.sh")
|
|
183
|
+
* @returns Absolute path to the bundled script in the Lisa install
|
|
184
|
+
*/
|
|
185
|
+
function resolveBundledScript(filename) {
|
|
186
|
+
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
187
|
+
return path.join(moduleDir, "scripts", filename);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Copy every .md file from Lisa's `plugins/lisa/rules/` into the host's
|
|
191
|
+
* `.codex/lisa-rules/`. Returns the list of filenames copied (without
|
|
192
|
+
* directory).
|
|
193
|
+
* @param lisaDir - Absolute path to the Lisa repo / installed package
|
|
194
|
+
* @param rulesDestDir - Absolute path to `<destDir>/.codex/lisa-rules/`
|
|
195
|
+
* @returns Filenames (without directory) of every rule .md file copied
|
|
196
|
+
*/
|
|
197
|
+
async function mirrorRules(lisaDir, rulesDestDir) {
|
|
198
|
+
const rulesSourceDir = path.join(lisaDir, "plugins", "lisa", "rules");
|
|
199
|
+
if (!(await fse.pathExists(rulesSourceDir))) {
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
const files = (await readdir(rulesSourceDir)).filter(name => name.endsWith(".md"));
|
|
203
|
+
await Promise.all(files.map(file => copyFile(path.join(rulesSourceDir, file), path.join(rulesDestDir, file))));
|
|
204
|
+
return Object.freeze(files);
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=hooks-installer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks-installer.js","sourceRoot":"","sources":["../../src/codex/hooks-installer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tagged-merge writer for `.codex/hooks.json`.
|
|
3
|
+
*
|
|
4
|
+
* Codex's hooks parser is not configured with `deny_unknown_fields` (verified
|
|
5
|
+
* against `codex-rs/config/src/hook_config.rs`), so Lisa can attach
|
|
6
|
+
* `_lisaManaged: true` and `_lisaId: "..."` marker fields to individual hook
|
|
7
|
+
* entries. On every install the merger:
|
|
8
|
+
*
|
|
9
|
+
* 1. Reads the existing hooks.json (if any)
|
|
10
|
+
* 2. Strips every entry where `_lisaManaged === true`
|
|
11
|
+
* 3. Appends the current Lisa hook set with markers attached
|
|
12
|
+
* 4. Preserves all entries the host added (no `_lisaManaged` marker)
|
|
13
|
+
*
|
|
14
|
+
* This mirrors the pattern Lisa uses for `enabledPlugins` deep-merge in
|
|
15
|
+
* `.claude/settings.json` — ownership is keyed on a marker field instead of
|
|
16
|
+
* a key path.
|
|
17
|
+
* @module codex/hooks-merger
|
|
18
|
+
*/
|
|
19
|
+
/** Markers attached to every Lisa-owned hook entry */
|
|
20
|
+
export declare const LISA_MANAGED_MARKER: "_lisaManaged";
|
|
21
|
+
export declare const LISA_ID_MARKER: "_lisaId";
|
|
22
|
+
/** A single hook handler entry (innermost: type/command/timeout/etc.) */
|
|
23
|
+
export interface HookHandler {
|
|
24
|
+
readonly type: string;
|
|
25
|
+
readonly command?: string;
|
|
26
|
+
readonly timeout?: number;
|
|
27
|
+
readonly statusMessage?: string;
|
|
28
|
+
/** Lisa ownership marker (set on Lisa-owned entries only) */
|
|
29
|
+
readonly [LISA_MANAGED_MARKER]?: boolean;
|
|
30
|
+
/** Stable identifier so logs can name the hook */
|
|
31
|
+
readonly [LISA_ID_MARKER]?: string;
|
|
32
|
+
}
|
|
33
|
+
/** A matcher group: a regex matcher + an array of handlers it triggers */
|
|
34
|
+
export interface MatcherGroup {
|
|
35
|
+
readonly matcher: string;
|
|
36
|
+
readonly hooks: readonly HookHandler[];
|
|
37
|
+
}
|
|
38
|
+
/** All hook events Codex understands */
|
|
39
|
+
export type CodexHookEvent = "PreToolUse" | "PostToolUse" | "UserPromptSubmit" | "Stop" | "SessionStart" | "PermissionRequest";
|
|
40
|
+
/** The shape of `.codex/hooks.json` */
|
|
41
|
+
export interface HooksFile {
|
|
42
|
+
readonly hooks?: Readonly<Partial<Record<CodexHookEvent, readonly MatcherGroup[]>>>;
|
|
43
|
+
}
|
|
44
|
+
/** A Lisa-owned hook to be installed (mutable input — markers added by merger) */
|
|
45
|
+
export interface LisaHookSpec {
|
|
46
|
+
/** Stable identifier; goes into `_lisaId` so logs can name the hook */
|
|
47
|
+
readonly id: string;
|
|
48
|
+
/** Codex hook event the entry applies to */
|
|
49
|
+
readonly event: CodexHookEvent;
|
|
50
|
+
/** Matcher regex for `matcher` field (use "" for events that don't match) */
|
|
51
|
+
readonly matcher: string;
|
|
52
|
+
/** Shell command to run */
|
|
53
|
+
readonly command: string;
|
|
54
|
+
/** Optional timeout in seconds (Codex default is 600) */
|
|
55
|
+
readonly timeout?: number;
|
|
56
|
+
/** Optional human-readable status message */
|
|
57
|
+
readonly statusMessage?: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Merge Lisa's current hook set into an existing hooks.json document,
|
|
61
|
+
* preserving any host-authored entries.
|
|
62
|
+
* @param existing - Parsed hooks.json (or {} if none)
|
|
63
|
+
* @param lisaHooks - Lisa-owned hook specs to install
|
|
64
|
+
* @returns New HooksFile object with Lisa entries replaced and host entries preserved
|
|
65
|
+
*/
|
|
66
|
+
export declare function mergeLisaHooks(existing: HooksFile, lisaHooks: readonly LisaHookSpec[]): HooksFile;
|
|
67
|
+
/**
|
|
68
|
+
* Serialize a HooksFile to JSON suitable for writing to disk. Two-space
|
|
69
|
+
* indent, trailing newline.
|
|
70
|
+
* @param file - Hooks file to serialize
|
|
71
|
+
* @returns JSON string ending with a newline
|
|
72
|
+
*/
|
|
73
|
+
export declare function serializeHooksFile(file: HooksFile): string;
|
|
74
|
+
/**
|
|
75
|
+
* Type guard: parse raw JSON into a HooksFile, validating only the shape we
|
|
76
|
+
* touch. We do NOT reject unknown fields since Codex's parser allows them
|
|
77
|
+
* and we want to preserve host extensions on round-trip.
|
|
78
|
+
* @param raw - Untrusted JSON string from disk
|
|
79
|
+
* @returns Parsed HooksFile
|
|
80
|
+
*/
|
|
81
|
+
export declare function parseHooksFile(raw: string): HooksFile;
|
|
82
|
+
//# sourceMappingURL=hooks-merger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks-merger.d.ts","sourceRoot":"","sources":["../../src/codex/hooks-merger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,sDAAsD;AACtD,eAAO,MAAM,mBAAmB,EAAG,cAAuB,CAAC;AAC3D,eAAO,MAAM,cAAc,EAAG,SAAkB,CAAC;AAEjD,yEAAyE;AACzE,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,6DAA6D;IAC7D,QAAQ,CAAC,CAAC,mBAAmB,CAAC,CAAC,EAAE,OAAO,CAAC;IACzC,kDAAkD;IAClD,QAAQ,CAAC,CAAC,cAAc,CAAC,CAAC,EAAE,MAAM,CAAC;CACpC;AAED,0EAA0E;AAC1E,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;CACxC;AAED,wCAAwC;AACxC,MAAM,MAAM,cAAc,GACtB,YAAY,GACZ,aAAa,GACb,kBAAkB,GAClB,MAAM,GACN,cAAc,GACd,mBAAmB,CAAC;AAExB,uCAAuC;AACvC,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CACvB,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,SAAS,YAAY,EAAE,CAAC,CAAC,CACzD,CAAC;CACH;AAED,kFAAkF;AAClF,MAAM,WAAW,YAAY;IAC3B,uEAAuE;IACvE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,4CAA4C;IAC5C,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,2BAA2B;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,yDAAyD;IACzD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,6CAA6C;IAC7C,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,SAAS,EACnB,SAAS,EAAE,SAAS,YAAY,EAAE,GACjC,SAAS,CAuCX;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAE1D;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAgBrD"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tagged-merge writer for `.codex/hooks.json`.
|
|
3
|
+
*
|
|
4
|
+
* Codex's hooks parser is not configured with `deny_unknown_fields` (verified
|
|
5
|
+
* against `codex-rs/config/src/hook_config.rs`), so Lisa can attach
|
|
6
|
+
* `_lisaManaged: true` and `_lisaId: "..."` marker fields to individual hook
|
|
7
|
+
* entries. On every install the merger:
|
|
8
|
+
*
|
|
9
|
+
* 1. Reads the existing hooks.json (if any)
|
|
10
|
+
* 2. Strips every entry where `_lisaManaged === true`
|
|
11
|
+
* 3. Appends the current Lisa hook set with markers attached
|
|
12
|
+
* 4. Preserves all entries the host added (no `_lisaManaged` marker)
|
|
13
|
+
*
|
|
14
|
+
* This mirrors the pattern Lisa uses for `enabledPlugins` deep-merge in
|
|
15
|
+
* `.claude/settings.json` — ownership is keyed on a marker field instead of
|
|
16
|
+
* a key path.
|
|
17
|
+
* @module codex/hooks-merger
|
|
18
|
+
*/
|
|
19
|
+
/** Markers attached to every Lisa-owned hook entry */
|
|
20
|
+
export const LISA_MANAGED_MARKER = "_lisaManaged";
|
|
21
|
+
export const LISA_ID_MARKER = "_lisaId";
|
|
22
|
+
/**
|
|
23
|
+
* Merge Lisa's current hook set into an existing hooks.json document,
|
|
24
|
+
* preserving any host-authored entries.
|
|
25
|
+
* @param existing - Parsed hooks.json (or {} if none)
|
|
26
|
+
* @param lisaHooks - Lisa-owned hook specs to install
|
|
27
|
+
* @returns New HooksFile object with Lisa entries replaced and host entries preserved
|
|
28
|
+
*/
|
|
29
|
+
export function mergeLisaHooks(existing, lisaHooks) {
|
|
30
|
+
// Step 1: drop every matcher-group that's pure-Lisa, strip Lisa handlers
|
|
31
|
+
// out of mixed-ownership groups, keep purely-host groups unchanged.
|
|
32
|
+
const existingEvents = Object.keys(existing.hooks ?? {});
|
|
33
|
+
const hostOnly = Object.fromEntries(existingEvents
|
|
34
|
+
.map(event => {
|
|
35
|
+
const filtered = stripLisaHandlers(existing.hooks?.[event] ?? []);
|
|
36
|
+
return [event, filtered];
|
|
37
|
+
})
|
|
38
|
+
.filter(([, groups]) => groups.length > 0));
|
|
39
|
+
// Step 2: append Lisa's current hook set, grouping by event
|
|
40
|
+
const lisaByEvent = lisaHooks.reduce((acc, spec) => {
|
|
41
|
+
const existingGroups = acc[spec.event] ?? [];
|
|
42
|
+
return {
|
|
43
|
+
...acc,
|
|
44
|
+
[spec.event]: [...existingGroups, lisaSpecToMatcherGroup(spec)],
|
|
45
|
+
};
|
|
46
|
+
}, {});
|
|
47
|
+
// Step 3: combine — for events touched by both, host first then Lisa
|
|
48
|
+
const allEvents = new Set([
|
|
49
|
+
...Object.keys(hostOnly),
|
|
50
|
+
...Object.keys(lisaByEvent),
|
|
51
|
+
]);
|
|
52
|
+
const merged = Object.fromEntries(Array.from(allEvents).map(event => [
|
|
53
|
+
event,
|
|
54
|
+
[...(hostOnly[event] ?? []), ...(lisaByEvent[event] ?? [])],
|
|
55
|
+
]));
|
|
56
|
+
return { hooks: merged };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Serialize a HooksFile to JSON suitable for writing to disk. Two-space
|
|
60
|
+
* indent, trailing newline.
|
|
61
|
+
* @param file - Hooks file to serialize
|
|
62
|
+
* @returns JSON string ending with a newline
|
|
63
|
+
*/
|
|
64
|
+
export function serializeHooksFile(file) {
|
|
65
|
+
return `${JSON.stringify(file, null, 2)}\n`;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Type guard: parse raw JSON into a HooksFile, validating only the shape we
|
|
69
|
+
* touch. We do NOT reject unknown fields since Codex's parser allows them
|
|
70
|
+
* and we want to preserve host extensions on round-trip.
|
|
71
|
+
* @param raw - Untrusted JSON string from disk
|
|
72
|
+
* @returns Parsed HooksFile
|
|
73
|
+
*/
|
|
74
|
+
export function parseHooksFile(raw) {
|
|
75
|
+
if (raw.trim().length === 0) {
|
|
76
|
+
return {};
|
|
77
|
+
}
|
|
78
|
+
const parsed = JSON.parse(raw);
|
|
79
|
+
if (parsed === null || typeof parsed !== "object") {
|
|
80
|
+
throw new Error("hooks.json must contain a JSON object at the root");
|
|
81
|
+
}
|
|
82
|
+
const obj = parsed;
|
|
83
|
+
if (obj.hooks === undefined) {
|
|
84
|
+
return {};
|
|
85
|
+
}
|
|
86
|
+
if (typeof obj.hooks !== "object" || obj.hooks === null) {
|
|
87
|
+
throw new Error("hooks.json: 'hooks' field must be an object");
|
|
88
|
+
}
|
|
89
|
+
return parsed;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Drop every Lisa-managed handler from a list of matcher groups. Groups
|
|
93
|
+
* whose only handlers were Lisa-managed are removed entirely. Groups with
|
|
94
|
+
* mixed ownership keep their host handlers.
|
|
95
|
+
* @param groups - Existing matcher groups for one event
|
|
96
|
+
* @returns The same groups with Lisa-managed handlers removed (empty groups dropped)
|
|
97
|
+
*/
|
|
98
|
+
function stripLisaHandlers(groups) {
|
|
99
|
+
return groups
|
|
100
|
+
.map(group => ({
|
|
101
|
+
matcher: group.matcher,
|
|
102
|
+
hooks: group.hooks.filter(handler => handler[LISA_MANAGED_MARKER] !== true),
|
|
103
|
+
}))
|
|
104
|
+
.filter(group => group.hooks.length > 0);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Convert a LisaHookSpec into a MatcherGroup with markers attached.
|
|
108
|
+
* @param spec - One Lisa hook spec to install
|
|
109
|
+
* @returns A matcher group containing one Lisa-marked handler
|
|
110
|
+
*/
|
|
111
|
+
function lisaSpecToMatcherGroup(spec) {
|
|
112
|
+
const handler = {
|
|
113
|
+
type: "command",
|
|
114
|
+
command: spec.command,
|
|
115
|
+
[LISA_MANAGED_MARKER]: true,
|
|
116
|
+
[LISA_ID_MARKER]: spec.id,
|
|
117
|
+
...(spec.timeout !== undefined ? { timeout: spec.timeout } : {}),
|
|
118
|
+
...(spec.statusMessage !== undefined
|
|
119
|
+
? { statusMessage: spec.statusMessage }
|
|
120
|
+
: {}),
|
|
121
|
+
};
|
|
122
|
+
return {
|
|
123
|
+
matcher: spec.matcher,
|
|
124
|
+
hooks: [handler],
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=hooks-merger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks-merger.js","sourceRoot":"","sources":["../../src/codex/hooks-merger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Filename of the Lisa-managed manifest, relative to `.codex/` */
|
|
2
|
+
export declare const LISA_MANAGED_MANIFEST_FILENAME = ".lisa-managed.json";
|
|
3
|
+
/** Schema of `.codex/.lisa-managed.json` */
|
|
4
|
+
export interface LisaManagedManifest {
|
|
5
|
+
/**
|
|
6
|
+
* Paths of files Lisa wrote, relative to the `.codex/` directory.
|
|
7
|
+
* Used to identify which files to delete when they stop being shipped.
|
|
8
|
+
*/
|
|
9
|
+
readonly files: readonly string[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Read the Lisa-managed manifest from `<destDir>/.codex/.lisa-managed.json`.
|
|
13
|
+
* Returns an empty manifest if the file doesn't exist.
|
|
14
|
+
* @param destDir - Absolute path to the destination project root
|
|
15
|
+
* @returns Parsed manifest (with empty file list if absent)
|
|
16
|
+
*/
|
|
17
|
+
export declare function readManagedManifest(destDir: string): Promise<LisaManagedManifest>;
|
|
18
|
+
/**
|
|
19
|
+
* Write the Lisa-managed manifest to disk, replacing any existing content.
|
|
20
|
+
* @param destDir - Absolute path to the destination project root
|
|
21
|
+
* @param files - Sorted list of relative-to-`.codex/` file paths Lisa shipped
|
|
22
|
+
*/
|
|
23
|
+
export declare function writeManagedManifest(destDir: string, files: readonly string[]): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Compute the set of stale files: in the previous manifest but not in the
|
|
26
|
+
* new shipment. These are candidates for deletion.
|
|
27
|
+
* @param previous - Manifest from the prior run
|
|
28
|
+
* @param current - File list Lisa is shipping this run (relative to `.codex/`)
|
|
29
|
+
* @returns Files that should be removed from the host project
|
|
30
|
+
*/
|
|
31
|
+
export declare function diffManifests(previous: LisaManagedManifest, current: readonly string[]): readonly string[];
|
|
32
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/codex/manifest.ts"],"names":[],"mappings":"AAuBA,mEAAmE;AACnE,eAAO,MAAM,8BAA8B,uBAAuB,CAAC;AAEnE,4CAA4C;AAC5C,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,mBAAmB,CAAC,CAY9B;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB,OAAO,CAAC,IAAI,CAAC,CAYf;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,mBAAmB,EAC7B,OAAO,EAAE,SAAS,MAAM,EAAE,GACzB,SAAS,MAAM,EAAE,CAGnB"}
|