litcodex-ai 0.3.21 → 0.3.25
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/README.md +9 -3
- package/node_modules/@litcodex/lit-loop/README.md +1 -1
- package/node_modules/@litcodex/lit-loop/directive.md +130 -0
- package/node_modules/@litcodex/lit-loop/directives/hyperplan.md +130 -0
- package/node_modules/@litcodex/lit-loop/directives/init-deep.md +140 -0
- package/node_modules/@litcodex/lit-loop/directives/lit-plan.md +130 -0
- package/node_modules/@litcodex/lit-loop/directives/lit-recap.md +131 -1
- package/node_modules/@litcodex/lit-loop/directives/litgoal.md +130 -0
- package/node_modules/@litcodex/lit-loop/directives/litresearch.md +148 -0
- package/node_modules/@litcodex/lit-loop/directives/litwork.md +130 -0
- package/node_modules/@litcodex/lit-loop/directives/review-work.md +130 -0
- package/node_modules/@litcodex/lit-loop/directives/start-work.md +130 -0
- package/node_modules/@litcodex/lit-loop/dist/codex-hook.js +2 -2
- package/node_modules/@litcodex/lit-loop/dist/directive.d.ts +6 -0
- package/node_modules/@litcodex/lit-loop/dist/directive.js +34 -0
- package/node_modules/@litcodex/lit-loop/dist/markers.d.ts +2 -0
- package/node_modules/@litcodex/lit-loop/dist/markers.js +2 -0
- package/node_modules/@litcodex/lit-loop/dist/modes.d.ts +5 -1
- package/node_modules/@litcodex/lit-loop/dist/modes.js +34 -1
- package/node_modules/@litcodex/lit-loop/dist/skill-mention-scope.d.ts +1 -0
- package/node_modules/@litcodex/lit-loop/dist/skill-mention-scope.js +16 -5
- package/node_modules/@litcodex/lit-loop/dist/state-store.js +36 -5
- package/node_modules/@litcodex/lit-loop/dist/trigger.d.ts +4 -3
- package/node_modules/@litcodex/lit-loop/dist/trigger.js +14 -14
- package/node_modules/@litcodex/lit-loop/package.json +5 -2
- package/node_modules/@litcodex/lit-loop/skills/hyperplan/SKILL.md +311 -0
- package/node_modules/@litcodex/lit-loop/skills/init-deep/SKILL.md +464 -0
- package/node_modules/@litcodex/lit-loop/skills/lit-loop/SKILL.md +195 -0
- package/node_modules/@litcodex/lit-loop/skills/lit-plan/SKILL.md +318 -0
- package/node_modules/@litcodex/lit-loop/skills/lit-recap/SKILL.md +209 -0
- package/node_modules/@litcodex/lit-loop/skills/litgoal/SKILL.md +203 -0
- package/node_modules/@litcodex/lit-loop/skills/litresearch/SKILL.md +479 -0
- package/node_modules/@litcodex/lit-loop/skills/litwork/SKILL.md +219 -0
- package/node_modules/@litcodex/lit-loop/skills/review-work/SKILL.md +828 -0
- package/node_modules/@litcodex/lit-loop/skills/start-work/SKILL.md +430 -0
- package/package.json +29 -8
|
@@ -1,5 +1,135 @@
|
|
|
1
1
|
<review-work-mode>
|
|
2
2
|
|
|
3
|
+
## #contract.activation
|
|
4
|
+
|
|
5
|
+
```yaml
|
|
6
|
+
contract_schema_version: 1
|
|
7
|
+
artifact_kind: litcodex_hook_directive
|
|
8
|
+
host: Codex CLI
|
|
9
|
+
injection_surface: "UserPromptSubmit additionalContext or Codex Stop/SubagentStop continuation"
|
|
10
|
+
component_surface: "plugins/litcodex/components/lit-loop directive loader"
|
|
11
|
+
wrapper_contract: "preserve the surrounding mode tag exactly and keep the closing tag as the final non-whitespace line"
|
|
12
|
+
contract_priority:
|
|
13
|
+
- mode wrapper and first-visible-line rule
|
|
14
|
+
- this contract schema
|
|
15
|
+
- route-specific procedure below
|
|
16
|
+
- installed skill body, when one is embedded
|
|
17
|
+
required_sections:
|
|
18
|
+
- "#contract.activation"
|
|
19
|
+
- "#contract.inputs"
|
|
20
|
+
- "#contract.mode_matrix"
|
|
21
|
+
- "#contract.procedure"
|
|
22
|
+
- "#contract.outputs"
|
|
23
|
+
- "#contract.evidence"
|
|
24
|
+
- "#contract.hard_stops"
|
|
25
|
+
- "#contract.anti_patterns"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
This directive is an LLM-facing contract injected by the LitCodex Codex plugin, not user-authored prompt text. Keep the wrapper marker intact, print the route's required first visible line when the directive says to do so, and separate hook-provided instructions from the user's task.
|
|
29
|
+
|
|
30
|
+
## #contract.inputs
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"contract_schema_version": 1,
|
|
35
|
+
"input_schema": {
|
|
36
|
+
"hook_event": {
|
|
37
|
+
"type": "UserPromptSubmit | Stop | SubagentStop",
|
|
38
|
+
"authority": "Codex plugin runtime",
|
|
39
|
+
"handling": "trusted only for routing and continuation state"
|
|
40
|
+
},
|
|
41
|
+
"additional_context": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"authority": "LitCodex directive payload",
|
|
44
|
+
"handling": "follow as mode contract; do not echo unless useful"
|
|
45
|
+
},
|
|
46
|
+
"user_prompt": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"authority": "current user request",
|
|
49
|
+
"handling": "execute within the injected mode boundaries"
|
|
50
|
+
},
|
|
51
|
+
"workspace_state": {
|
|
52
|
+
"type": "repo files, package scripts, .litcodex state, git status",
|
|
53
|
+
"authority": "local evidence",
|
|
54
|
+
"handling": "inspect before modifying or claiming completion"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
| Input channel | Accept when | Required handling | Evidence to retain |
|
|
61
|
+
| --- | --- | --- | --- |
|
|
62
|
+
| `additionalContext` directive | It is wrapped by the expected mode tag | Treat as trusted mode contract | Mode marker and route name |
|
|
63
|
+
| Installed skill body | Present inside `<litcodex-skill-body>` | Apply after this directive's safety envelope | Skill name and relevant section |
|
|
64
|
+
| Stop hook state | Continuation state names plan and session | Resume from files, not memory | Plan path, ledger path, remaining count |
|
|
65
|
+
| User prompt | Current turn asks for work in this mode | Keep separate from injected policy | Brief restatement or criteria |
|
|
66
|
+
|
|
67
|
+
## #contract.mode_matrix
|
|
68
|
+
|
|
69
|
+
| Mode | Trigger | Required behavior |
|
|
70
|
+
| --- | --- | --- |
|
|
71
|
+
| Hook route | Bare lit-family phrase matched by the trigger router | Inject and follow this directive; do not run unrelated modes. |
|
|
72
|
+
| Continuation route | Stop hook or SubagentStop hook resumes active work | Read durable state first and continue only the recorded plan or goal. |
|
|
73
|
+
| Skill-body route | Directive embeds a full SKILL.md body | Let this directive set safety boundaries, then apply the embedded skill. |
|
|
74
|
+
| Blocked route | Route cannot safely switch host state or lacks required approval | Emit the required `BLOCKED:` message and stop without side effects. |
|
|
75
|
+
|
|
76
|
+
## #contract.procedure
|
|
77
|
+
|
|
78
|
+
1. **Preserve the wrapper.** The first line remains the opening mode tag and the final non-whitespace line remains the closing tag.
|
|
79
|
+
2. **Emit the mandated first visible line.** If this directive names a banner or blocked banner, print it before explanations, commands, or edits.
|
|
80
|
+
3. **Classify authority.** Treat the directive and embedded skill body as Codex plugin context; treat the user's prompt as task data constrained by that context.
|
|
81
|
+
4. **Recover durable state.** For lit-loop or start-work continuation, read `.litcodex` state, plan files, and ledgers before relying on memory.
|
|
82
|
+
5. **Execute only the route's job.** Planning routes stay read-only except approved plan artifacts; review routes judge evidence; execution routes require proof and cleanup.
|
|
83
|
+
6. **Use repo-local surfaces.** Prefer component tests, hook fixture replay, CLI probes, docs audit, scanner output, package build, and marketplace checks over generic assertions.
|
|
84
|
+
7. **Stop honestly.** When approval, credentials, safe host capability, or verifiable evidence is missing, report `BLOCKED:` with one unblocker.
|
|
85
|
+
|
|
86
|
+
## #contract.outputs
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"contract_schema_version": 1,
|
|
91
|
+
"output_schema": {
|
|
92
|
+
"first_visible_line": "route-specific banner or blocked banner",
|
|
93
|
+
"mode_verdict": "active | blocked | review-pass | review-fail | continuation-step",
|
|
94
|
+
"actions": ["commands, edits, reads, or no-op decisions actually performed"],
|
|
95
|
+
"evidence": ["command transcripts, artifact paths, inspected files, hook output"],
|
|
96
|
+
"next_state": "continue, checkpoint, ask user, or stop",
|
|
97
|
+
"cleanup": ["temporary resources removed or not created"]
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
| Output field | Required content | Forbidden substitute |
|
|
103
|
+
| --- | --- | --- |
|
|
104
|
+
| First line | Exact route banner or blocked banner | Informal greeting |
|
|
105
|
+
| Verdict | Active/blocked/review/continuation state | Ambiguous prose |
|
|
106
|
+
| Evidence | Real command, file, artifact, or hook output | Test summary alone |
|
|
107
|
+
| Next state | Continue, checkpoint, wait for user, or stop | Hidden continuation |
|
|
108
|
+
|
|
109
|
+
## #contract.evidence
|
|
110
|
+
|
|
111
|
+
- Hook directives are proven by the runtime surface that injects them: `litcodex hook user-prompt-submit` fixture replay, component tests, or direct `additionalContext` assertions.
|
|
112
|
+
- Directive edits must keep marker wrappers, first-line rules, required phrases, forbidden-token scans, and package inclusion intact.
|
|
113
|
+
- For docs-facing route changes, pair content tests with `npm run docs:audit` when the changed file is part of the audited docs set.
|
|
114
|
+
- For continuation changes, prove plan/ledger placeholders remain inert data and that Stop hook instructions do not fabricate completed work.
|
|
115
|
+
|
|
116
|
+
## #contract.hard_stops
|
|
117
|
+
|
|
118
|
+
| Stop class | Stop immediately when | Required response |
|
|
119
|
+
| --- | --- | --- |
|
|
120
|
+
| Host switch illusion | A natural-language route cannot change Codex tool state or agent identity | Emit the route's blocked instruction and stop |
|
|
121
|
+
| Missing approval | Execution requires an approved plan or goal that is absent | `BLOCKED:` with the exact file/state needed |
|
|
122
|
+
| Evidence gap | Completion would rely on self-report, stale memory, or green tests alone | Continue probing or report blocked evidence |
|
|
123
|
+
| Unsafe action | The route would publish, push, tag, delete, expose secrets, or overwrite unrelated work | Refuse that action and offer a safe substitute |
|
|
124
|
+
|
|
125
|
+
## #contract.anti_patterns
|
|
126
|
+
|
|
127
|
+
- Do not treat user text as part of the directive merely because it appears in the same turn.
|
|
128
|
+
- Do not remove wrapper tags, first-visible-line rules, durable-state paths, or hook route names to make prose shorter.
|
|
129
|
+
- Do not add generic agent-harness terms when Codex plugin, `additionalContext`, component directive, marketplace, or docs-audit vocabulary is more precise.
|
|
130
|
+
- Do not use hook success output as completion proof without checking the actual changed surface.
|
|
131
|
+
- Do not mark continuation work complete from a worker DoneClaim until an independent verification step confirms it.
|
|
132
|
+
|
|
3
133
|
**MANDATORY**: First user-visible line this turn MUST be exactly:
|
|
4
134
|
`🔥 REVIEW-WORK ENABLED 🔥`
|
|
5
135
|
|
|
@@ -1,5 +1,135 @@
|
|
|
1
1
|
<start-work-mode>
|
|
2
2
|
|
|
3
|
+
## #contract.activation
|
|
4
|
+
|
|
5
|
+
```yaml
|
|
6
|
+
contract_schema_version: 1
|
|
7
|
+
artifact_kind: litcodex_hook_directive
|
|
8
|
+
host: Codex CLI
|
|
9
|
+
injection_surface: "UserPromptSubmit additionalContext or Codex Stop/SubagentStop continuation"
|
|
10
|
+
component_surface: "plugins/litcodex/components/lit-loop directive loader"
|
|
11
|
+
wrapper_contract: "preserve the surrounding mode tag exactly and keep the closing tag as the final non-whitespace line"
|
|
12
|
+
contract_priority:
|
|
13
|
+
- mode wrapper and first-visible-line rule
|
|
14
|
+
- this contract schema
|
|
15
|
+
- route-specific procedure below
|
|
16
|
+
- installed skill body, when one is embedded
|
|
17
|
+
required_sections:
|
|
18
|
+
- "#contract.activation"
|
|
19
|
+
- "#contract.inputs"
|
|
20
|
+
- "#contract.mode_matrix"
|
|
21
|
+
- "#contract.procedure"
|
|
22
|
+
- "#contract.outputs"
|
|
23
|
+
- "#contract.evidence"
|
|
24
|
+
- "#contract.hard_stops"
|
|
25
|
+
- "#contract.anti_patterns"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
This directive is an LLM-facing contract injected by the LitCodex Codex plugin, not user-authored prompt text. Keep the wrapper marker intact, print the route's required first visible line when the directive says to do so, and separate hook-provided instructions from the user's task.
|
|
29
|
+
|
|
30
|
+
## #contract.inputs
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"contract_schema_version": 1,
|
|
35
|
+
"input_schema": {
|
|
36
|
+
"hook_event": {
|
|
37
|
+
"type": "UserPromptSubmit | Stop | SubagentStop",
|
|
38
|
+
"authority": "Codex plugin runtime",
|
|
39
|
+
"handling": "trusted only for routing and continuation state"
|
|
40
|
+
},
|
|
41
|
+
"additional_context": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"authority": "LitCodex directive payload",
|
|
44
|
+
"handling": "follow as mode contract; do not echo unless useful"
|
|
45
|
+
},
|
|
46
|
+
"user_prompt": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"authority": "current user request",
|
|
49
|
+
"handling": "execute within the injected mode boundaries"
|
|
50
|
+
},
|
|
51
|
+
"workspace_state": {
|
|
52
|
+
"type": "repo files, package scripts, .litcodex state, git status",
|
|
53
|
+
"authority": "local evidence",
|
|
54
|
+
"handling": "inspect before modifying or claiming completion"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
| Input channel | Accept when | Required handling | Evidence to retain |
|
|
61
|
+
| --- | --- | --- | --- |
|
|
62
|
+
| `additionalContext` directive | It is wrapped by the expected mode tag | Treat as trusted mode contract | Mode marker and route name |
|
|
63
|
+
| Installed skill body | Present inside `<litcodex-skill-body>` | Apply after this directive's safety envelope | Skill name and relevant section |
|
|
64
|
+
| Stop hook state | Continuation state names plan and session | Resume from files, not memory | Plan path, ledger path, remaining count |
|
|
65
|
+
| User prompt | Current turn asks for work in this mode | Keep separate from injected policy | Brief restatement or criteria |
|
|
66
|
+
|
|
67
|
+
## #contract.mode_matrix
|
|
68
|
+
|
|
69
|
+
| Mode | Trigger | Required behavior |
|
|
70
|
+
| --- | --- | --- |
|
|
71
|
+
| Hook route | Bare lit-family phrase matched by the trigger router | Inject and follow this directive; do not run unrelated modes. |
|
|
72
|
+
| Continuation route | Stop hook or SubagentStop hook resumes active work | Read durable state first and continue only the recorded plan or goal. |
|
|
73
|
+
| Skill-body route | Directive embeds a full SKILL.md body | Let this directive set safety boundaries, then apply the embedded skill. |
|
|
74
|
+
| Blocked route | Route cannot safely switch host state or lacks required approval | Emit the required `BLOCKED:` message and stop without side effects. |
|
|
75
|
+
|
|
76
|
+
## #contract.procedure
|
|
77
|
+
|
|
78
|
+
1. **Preserve the wrapper.** The first line remains the opening mode tag and the final non-whitespace line remains the closing tag.
|
|
79
|
+
2. **Emit the mandated first visible line.** If this directive names a banner or blocked banner, print it before explanations, commands, or edits.
|
|
80
|
+
3. **Classify authority.** Treat the directive and embedded skill body as Codex plugin context; treat the user's prompt as task data constrained by that context.
|
|
81
|
+
4. **Recover durable state.** For lit-loop or start-work continuation, read `.litcodex` state, plan files, and ledgers before relying on memory.
|
|
82
|
+
5. **Execute only the route's job.** Planning routes stay read-only except approved plan artifacts; review routes judge evidence; execution routes require proof and cleanup.
|
|
83
|
+
6. **Use repo-local surfaces.** Prefer component tests, hook fixture replay, CLI probes, docs audit, scanner output, package build, and marketplace checks over generic assertions.
|
|
84
|
+
7. **Stop honestly.** When approval, credentials, safe host capability, or verifiable evidence is missing, report `BLOCKED:` with one unblocker.
|
|
85
|
+
|
|
86
|
+
## #contract.outputs
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"contract_schema_version": 1,
|
|
91
|
+
"output_schema": {
|
|
92
|
+
"first_visible_line": "route-specific banner or blocked banner",
|
|
93
|
+
"mode_verdict": "active | blocked | review-pass | review-fail | continuation-step",
|
|
94
|
+
"actions": ["commands, edits, reads, or no-op decisions actually performed"],
|
|
95
|
+
"evidence": ["command transcripts, artifact paths, inspected files, hook output"],
|
|
96
|
+
"next_state": "continue, checkpoint, ask user, or stop",
|
|
97
|
+
"cleanup": ["temporary resources removed or not created"]
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
| Output field | Required content | Forbidden substitute |
|
|
103
|
+
| --- | --- | --- |
|
|
104
|
+
| First line | Exact route banner or blocked banner | Informal greeting |
|
|
105
|
+
| Verdict | Active/blocked/review/continuation state | Ambiguous prose |
|
|
106
|
+
| Evidence | Real command, file, artifact, or hook output | Test summary alone |
|
|
107
|
+
| Next state | Continue, checkpoint, wait for user, or stop | Hidden continuation |
|
|
108
|
+
|
|
109
|
+
## #contract.evidence
|
|
110
|
+
|
|
111
|
+
- Hook directives are proven by the runtime surface that injects them: `litcodex hook user-prompt-submit` fixture replay, component tests, or direct `additionalContext` assertions.
|
|
112
|
+
- Directive edits must keep marker wrappers, first-line rules, required phrases, forbidden-token scans, and package inclusion intact.
|
|
113
|
+
- For docs-facing route changes, pair content tests with `npm run docs:audit` when the changed file is part of the audited docs set.
|
|
114
|
+
- For continuation changes, prove plan/ledger placeholders remain inert data and that Stop hook instructions do not fabricate completed work.
|
|
115
|
+
|
|
116
|
+
## #contract.hard_stops
|
|
117
|
+
|
|
118
|
+
| Stop class | Stop immediately when | Required response |
|
|
119
|
+
| --- | --- | --- |
|
|
120
|
+
| Host switch illusion | A natural-language route cannot change Codex tool state or agent identity | Emit the route's blocked instruction and stop |
|
|
121
|
+
| Missing approval | Execution requires an approved plan or goal that is absent | `BLOCKED:` with the exact file/state needed |
|
|
122
|
+
| Evidence gap | Completion would rely on self-report, stale memory, or green tests alone | Continue probing or report blocked evidence |
|
|
123
|
+
| Unsafe action | The route would publish, push, tag, delete, expose secrets, or overwrite unrelated work | Refuse that action and offer a safe substitute |
|
|
124
|
+
|
|
125
|
+
## #contract.anti_patterns
|
|
126
|
+
|
|
127
|
+
- Do not treat user text as part of the directive merely because it appears in the same turn.
|
|
128
|
+
- Do not remove wrapper tags, first-visible-line rules, durable-state paths, or hook route names to make prose shorter.
|
|
129
|
+
- Do not add generic agent-harness terms when Codex plugin, `additionalContext`, component directive, marketplace, or docs-audit vocabulary is more precise.
|
|
130
|
+
- Do not use hook success output as completion proof without checking the actual changed surface.
|
|
131
|
+
- Do not mark continuation work complete from a worker DoneClaim until an independent verification step confirms it.
|
|
132
|
+
|
|
3
133
|
**MANDATORY**: First user-visible line this turn MUST be exactly:
|
|
4
134
|
`🔥 START-WORK BLOCKED 🔥`
|
|
5
135
|
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
// On activation the payload is the trusted bundled directive, loaded behind a FAIL-SILENT boundary:
|
|
18
18
|
// a directive-load error resolves to "" (→ noop), so a missing dist/directive.md degrades the hook
|
|
19
19
|
// to a silent no-op + exit 0 rather than throwing into the Codex host.
|
|
20
|
-
import {
|
|
20
|
+
import { loadDirectiveWithSkillBodyFrom } from "./directive.js";
|
|
21
21
|
import { shouldSuppressInjection } from "./guards.js";
|
|
22
22
|
import { modeForToken } from "./modes.js";
|
|
23
23
|
import { matchLitTrigger } from "./trigger.js";
|
|
@@ -125,7 +125,7 @@ export function applyPreToolUseCreateGoalGuard(input) {
|
|
|
125
125
|
*/
|
|
126
126
|
function loadDirectiveForModeFailSilent(mode) {
|
|
127
127
|
try {
|
|
128
|
-
return
|
|
128
|
+
return loadDirectiveWithSkillBodyFrom(mode.directivePath, mode.openMarker, mode.closeMarker, mode.skillName, mode.skillPath);
|
|
129
129
|
}
|
|
130
130
|
catch {
|
|
131
131
|
return "";
|
|
@@ -14,6 +14,12 @@ export declare class LitLoopDirectiveError extends Error {
|
|
|
14
14
|
* Normalization: CRLF→LF, lone CR→LF, then trim(). Fail-loud with a typed error (codes are generic).
|
|
15
15
|
*/
|
|
16
16
|
export declare function loadDirectiveFrom(resolvedPath: string, openMarker: string, closeMarker: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* Load a mode directive and splice the matching bundled SKILL.md body inside the same wrapper.
|
|
19
|
+
* This keeps the small host-adapted directive as the safety envelope while satisfying the
|
|
20
|
+
* bare-invocation contract: the model receives the full installed skill text for the selected mode.
|
|
21
|
+
*/
|
|
22
|
+
export declare function loadDirectiveWithSkillBodyFrom(resolvedPath: string, openMarker: string, closeMarker: string, skillName: string, skillPath: string): string;
|
|
17
23
|
/**
|
|
18
24
|
* Read + normalize + validate the lit-loop directive at an explicit path. Test-only seam (S10
|
|
19
25
|
* §Test plan) so corrupt / CRLF / missing-file cases can be exercised without swapping the bundled
|
|
@@ -55,6 +55,40 @@ export function loadDirectiveFrom(resolvedPath, openMarker, closeMarker) {
|
|
|
55
55
|
}
|
|
56
56
|
return normalized;
|
|
57
57
|
}
|
|
58
|
+
function readNormalizedBody(resolvedPath, label) {
|
|
59
|
+
let raw;
|
|
60
|
+
try {
|
|
61
|
+
raw = readFileSync(resolvedPath, "utf8");
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
65
|
+
throw new LitLoopDirectiveError("LIT_LOOP_DIRECTIVE_UNREADABLE", `${label}: unreadable at ${resolvedPath}: ${detail}`, resolvedPath);
|
|
66
|
+
}
|
|
67
|
+
const normalized = raw.replace(/\r\n/g, "\n").replace(/\r/g, "\n").trim();
|
|
68
|
+
if (normalized.length === 0) {
|
|
69
|
+
throw new LitLoopDirectiveError("LIT_LOOP_DIRECTIVE_EMPTY", `${label}: empty after normalization at ${resolvedPath}`, resolvedPath);
|
|
70
|
+
}
|
|
71
|
+
return normalized;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Load a mode directive and splice the matching bundled SKILL.md body inside the same wrapper.
|
|
75
|
+
* This keeps the small host-adapted directive as the safety envelope while satisfying the
|
|
76
|
+
* bare-invocation contract: the model receives the full installed skill text for the selected mode.
|
|
77
|
+
*/
|
|
78
|
+
export function loadDirectiveWithSkillBodyFrom(resolvedPath, openMarker, closeMarker, skillName, skillPath) {
|
|
79
|
+
const directive = loadDirectiveFrom(resolvedPath, openMarker, closeMarker);
|
|
80
|
+
const skillBody = readNormalizedBody(skillPath, `skill body ${skillName}`);
|
|
81
|
+
const bodyBlock = [
|
|
82
|
+
"",
|
|
83
|
+
"# Installed skill body",
|
|
84
|
+
"",
|
|
85
|
+
`<litcodex-skill-body name="${skillName}">`,
|
|
86
|
+
skillBody,
|
|
87
|
+
"</litcodex-skill-body>",
|
|
88
|
+
"",
|
|
89
|
+
].join("\n");
|
|
90
|
+
return `${directive.slice(0, -closeMarker.length).trimEnd()}${bodyBlock}${closeMarker}`;
|
|
91
|
+
}
|
|
58
92
|
/**
|
|
59
93
|
* Read + normalize + validate the lit-loop directive at an explicit path. Test-only seam (S10
|
|
60
94
|
* §Test plan) so corrupt / CRLF / missing-file cases can be exercised without swapping the bundled
|
|
@@ -16,4 +16,6 @@ export declare const LIT_RECAP_DIRECTIVE_MARKER: "<lit-recap-mode>";
|
|
|
16
16
|
export declare const LIT_RECAP_DIRECTIVE_CLOSE: "</lit-recap-mode>";
|
|
17
17
|
export declare const HYPERPLAN_DIRECTIVE_MARKER: "<hyperplan-mode>";
|
|
18
18
|
export declare const HYPERPLAN_DIRECTIVE_CLOSE: "</hyperplan-mode>";
|
|
19
|
+
export declare const INIT_DEEP_DIRECTIVE_MARKER: "<init-deep-mode>";
|
|
20
|
+
export declare const INIT_DEEP_DIRECTIVE_CLOSE: "</init-deep-mode>";
|
|
19
21
|
export type LitLoopDirectiveMarker = typeof LIT_LOOP_DIRECTIVE_MARKER;
|
|
@@ -22,3 +22,5 @@ export const LIT_RECAP_DIRECTIVE_MARKER = "<lit-recap-mode>";
|
|
|
22
22
|
export const LIT_RECAP_DIRECTIVE_CLOSE = "</lit-recap-mode>";
|
|
23
23
|
export const HYPERPLAN_DIRECTIVE_MARKER = "<hyperplan-mode>";
|
|
24
24
|
export const HYPERPLAN_DIRECTIVE_CLOSE = "</hyperplan-mode>";
|
|
25
|
+
export const INIT_DEEP_DIRECTIVE_MARKER = "<init-deep-mode>";
|
|
26
|
+
export const INIT_DEEP_DIRECTIVE_CLOSE = "</init-deep-mode>";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { LitTriggerToken } from "./trigger.js";
|
|
2
2
|
/** Lit-family modes. Multiple tokens may map to one mode (lit/litcodex/lit-loop → lit-loop). */
|
|
3
|
-
export type LitMode = "hyperplan" | "lit-loop" | "litwork" | "lit-plan" | "litgoal" | "review-work" | "litresearch" | "start-work" | "lit-recap";
|
|
3
|
+
export type LitMode = "hyperplan" | "init-deep" | "lit-loop" | "litwork" | "lit-plan" | "litgoal" | "review-work" | "litresearch" | "start-work" | "lit-recap";
|
|
4
4
|
/** Per-mode routing spec: its marker pair (guard) + resolved directive path (loader). */
|
|
5
5
|
export interface LitModeSpec {
|
|
6
6
|
readonly mode: LitMode;
|
|
@@ -8,6 +8,10 @@ export interface LitModeSpec {
|
|
|
8
8
|
readonly closeMarker: string;
|
|
9
9
|
/** Absolute path to the mode's directive.md, resolved from this module. */
|
|
10
10
|
readonly directivePath: string;
|
|
11
|
+
/** The bundled skill whose full SKILL.md body must be injected for this mode. */
|
|
12
|
+
readonly skillName: string;
|
|
13
|
+
/** Absolute path to the bundled skill body. */
|
|
14
|
+
readonly skillPath: string;
|
|
11
15
|
}
|
|
12
16
|
/** Token → mode spec. Frozen; the single source the hook router branches on. */
|
|
13
17
|
export declare const MODE_BY_TOKEN: Readonly<Record<LitTriggerToken, LitModeSpec>>;
|
|
@@ -13,18 +13,27 @@
|
|
|
13
13
|
// loader reads lazily + fail-silent, so a not-yet-authored directive simply degrades to a noop.
|
|
14
14
|
//
|
|
15
15
|
// Imports markers + the trigger token type only (no cycle: hook → modes → {markers, trigger}).
|
|
16
|
+
import { existsSync } from "node:fs";
|
|
16
17
|
import { fileURLToPath } from "node:url";
|
|
17
|
-
import { HYPERPLAN_DIRECTIVE_CLOSE, HYPERPLAN_DIRECTIVE_MARKER, LIT_LOOP_DIRECTIVE_CLOSE, LIT_LOOP_DIRECTIVE_MARKER, LIT_PLAN_DIRECTIVE_CLOSE, LIT_PLAN_DIRECTIVE_MARKER, LIT_RECAP_DIRECTIVE_CLOSE, LIT_RECAP_DIRECTIVE_MARKER, LITGOAL_DIRECTIVE_CLOSE, LITGOAL_DIRECTIVE_MARKER, LITRESEARCH_DIRECTIVE_CLOSE, LITRESEARCH_DIRECTIVE_MARKER, LITWORK_DIRECTIVE_CLOSE, LITWORK_DIRECTIVE_MARKER, REVIEW_WORK_DIRECTIVE_CLOSE, REVIEW_WORK_DIRECTIVE_MARKER, START_WORK_DIRECTIVE_CLOSE, START_WORK_DIRECTIVE_MARKER, } from "./markers.js";
|
|
18
|
+
import { HYPERPLAN_DIRECTIVE_CLOSE, HYPERPLAN_DIRECTIVE_MARKER, INIT_DEEP_DIRECTIVE_CLOSE, INIT_DEEP_DIRECTIVE_MARKER, LIT_LOOP_DIRECTIVE_CLOSE, LIT_LOOP_DIRECTIVE_MARKER, LIT_PLAN_DIRECTIVE_CLOSE, LIT_PLAN_DIRECTIVE_MARKER, LIT_RECAP_DIRECTIVE_CLOSE, LIT_RECAP_DIRECTIVE_MARKER, LITGOAL_DIRECTIVE_CLOSE, LITGOAL_DIRECTIVE_MARKER, LITRESEARCH_DIRECTIVE_CLOSE, LITRESEARCH_DIRECTIVE_MARKER, LITWORK_DIRECTIVE_CLOSE, LITWORK_DIRECTIVE_MARKER, REVIEW_WORK_DIRECTIVE_CLOSE, REVIEW_WORK_DIRECTIVE_MARKER, START_WORK_DIRECTIVE_CLOSE, START_WORK_DIRECTIVE_MARKER, } from "./markers.js";
|
|
18
19
|
/** Resolve a path relative to this module (percent-decoding, space/#/Hangul-safe). */
|
|
19
20
|
function resolveFromHere(rel) {
|
|
20
21
|
return fileURLToPath(new URL(rel, import.meta.url));
|
|
21
22
|
}
|
|
23
|
+
function resolveSkillPath(name) {
|
|
24
|
+
const devTree = resolveFromHere(`../../../skills/${name}/SKILL.md`);
|
|
25
|
+
if (existsSync(devTree))
|
|
26
|
+
return devTree;
|
|
27
|
+
return resolveFromHere(`../skills/${name}/SKILL.md`);
|
|
28
|
+
}
|
|
22
29
|
// lit-loop keeps the component-root directive.md (A3 G6); shared by lit / litcodex / lit-loop tokens.
|
|
23
30
|
const LIT_LOOP_SPEC = Object.freeze({
|
|
24
31
|
mode: "lit-loop",
|
|
25
32
|
openMarker: LIT_LOOP_DIRECTIVE_MARKER,
|
|
26
33
|
closeMarker: LIT_LOOP_DIRECTIVE_CLOSE,
|
|
27
34
|
directivePath: resolveFromHere("../directive.md"),
|
|
35
|
+
skillName: "lit-loop",
|
|
36
|
+
skillPath: resolveSkillPath("lit-loop"),
|
|
28
37
|
});
|
|
29
38
|
/** Token → mode spec. Frozen; the single source the hook router branches on. */
|
|
30
39
|
export const MODE_BY_TOKEN = Object.freeze({
|
|
@@ -33,6 +42,16 @@ export const MODE_BY_TOKEN = Object.freeze({
|
|
|
33
42
|
openMarker: HYPERPLAN_DIRECTIVE_MARKER,
|
|
34
43
|
closeMarker: HYPERPLAN_DIRECTIVE_CLOSE,
|
|
35
44
|
directivePath: resolveFromHere("../directives/hyperplan.md"),
|
|
45
|
+
skillName: "hyperplan",
|
|
46
|
+
skillPath: resolveSkillPath("hyperplan"),
|
|
47
|
+
}),
|
|
48
|
+
"init-deep": Object.freeze({
|
|
49
|
+
mode: "init-deep",
|
|
50
|
+
openMarker: INIT_DEEP_DIRECTIVE_MARKER,
|
|
51
|
+
closeMarker: INIT_DEEP_DIRECTIVE_CLOSE,
|
|
52
|
+
directivePath: resolveFromHere("../directives/init-deep.md"),
|
|
53
|
+
skillName: "init-deep",
|
|
54
|
+
skillPath: resolveSkillPath("init-deep"),
|
|
36
55
|
}),
|
|
37
56
|
"lit-loop": LIT_LOOP_SPEC,
|
|
38
57
|
litcodex: LIT_LOOP_SPEC,
|
|
@@ -42,42 +61,56 @@ export const MODE_BY_TOKEN = Object.freeze({
|
|
|
42
61
|
openMarker: LITWORK_DIRECTIVE_MARKER,
|
|
43
62
|
closeMarker: LITWORK_DIRECTIVE_CLOSE,
|
|
44
63
|
directivePath: resolveFromHere("../directives/litwork.md"),
|
|
64
|
+
skillName: "litwork",
|
|
65
|
+
skillPath: resolveSkillPath("litwork"),
|
|
45
66
|
}),
|
|
46
67
|
"lit-plan": Object.freeze({
|
|
47
68
|
mode: "lit-plan",
|
|
48
69
|
openMarker: LIT_PLAN_DIRECTIVE_MARKER,
|
|
49
70
|
closeMarker: LIT_PLAN_DIRECTIVE_CLOSE,
|
|
50
71
|
directivePath: resolveFromHere("../directives/lit-plan.md"),
|
|
72
|
+
skillName: "lit-plan",
|
|
73
|
+
skillPath: resolveSkillPath("lit-plan"),
|
|
51
74
|
}),
|
|
52
75
|
litgoal: Object.freeze({
|
|
53
76
|
mode: "litgoal",
|
|
54
77
|
openMarker: LITGOAL_DIRECTIVE_MARKER,
|
|
55
78
|
closeMarker: LITGOAL_DIRECTIVE_CLOSE,
|
|
56
79
|
directivePath: resolveFromHere("../directives/litgoal.md"),
|
|
80
|
+
skillName: "litgoal",
|
|
81
|
+
skillPath: resolveSkillPath("litgoal"),
|
|
57
82
|
}),
|
|
58
83
|
"review-work": Object.freeze({
|
|
59
84
|
mode: "review-work",
|
|
60
85
|
openMarker: REVIEW_WORK_DIRECTIVE_MARKER,
|
|
61
86
|
closeMarker: REVIEW_WORK_DIRECTIVE_CLOSE,
|
|
62
87
|
directivePath: resolveFromHere("../directives/review-work.md"),
|
|
88
|
+
skillName: "review-work",
|
|
89
|
+
skillPath: resolveSkillPath("review-work"),
|
|
63
90
|
}),
|
|
64
91
|
litresearch: Object.freeze({
|
|
65
92
|
mode: "litresearch",
|
|
66
93
|
openMarker: LITRESEARCH_DIRECTIVE_MARKER,
|
|
67
94
|
closeMarker: LITRESEARCH_DIRECTIVE_CLOSE,
|
|
68
95
|
directivePath: resolveFromHere("../directives/litresearch.md"),
|
|
96
|
+
skillName: "litresearch",
|
|
97
|
+
skillPath: resolveSkillPath("litresearch"),
|
|
69
98
|
}),
|
|
70
99
|
"start-work": Object.freeze({
|
|
71
100
|
mode: "start-work",
|
|
72
101
|
openMarker: START_WORK_DIRECTIVE_MARKER,
|
|
73
102
|
closeMarker: START_WORK_DIRECTIVE_CLOSE,
|
|
74
103
|
directivePath: resolveFromHere("../directives/start-work.md"),
|
|
104
|
+
skillName: "start-work",
|
|
105
|
+
skillPath: resolveSkillPath("start-work"),
|
|
75
106
|
}),
|
|
76
107
|
"lit-recap": Object.freeze({
|
|
77
108
|
mode: "lit-recap",
|
|
78
109
|
openMarker: LIT_RECAP_DIRECTIVE_MARKER,
|
|
79
110
|
closeMarker: LIT_RECAP_DIRECTIVE_CLOSE,
|
|
80
111
|
directivePath: resolveFromHere("../directives/lit-recap.md"),
|
|
112
|
+
skillName: "lit-recap",
|
|
113
|
+
skillPath: resolveSkillPath("lit-recap"),
|
|
81
114
|
}),
|
|
82
115
|
});
|
|
83
116
|
/** The mode spec for a matched token. Total over the token union (every token has a spec). */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import type { LitTriggerToken } from "./trigger.js";
|
|
2
2
|
export declare function isCodexSkillMentionToken(prompt: string, tokenIndex: number): boolean;
|
|
3
3
|
export declare function scopedTokenForBareLit(prompt: string, tokenIndex: number): LitTriggerToken | null;
|
|
4
|
+
export declare function suppressesBareLitAfterCodexSkillMention(prompt: string, tokenIndex: number): boolean;
|
|
@@ -13,19 +13,28 @@ export function isCodexSkillMentionToken(prompt, tokenIndex) {
|
|
|
13
13
|
return mention.startsWith("$") && mention.includes(":") && tokenIndex > start;
|
|
14
14
|
}
|
|
15
15
|
export function scopedTokenForBareLit(prompt, tokenIndex) {
|
|
16
|
-
|
|
16
|
+
const skillName = nearestLitcodexSkillNameBefore(prompt, tokenIndex);
|
|
17
|
+
if (skillName === null || suppressesBareLitAfterCodexSkillMention(prompt, tokenIndex)) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return tokenForSkillName(skillName);
|
|
21
|
+
}
|
|
22
|
+
export function suppressesBareLitAfterCodexSkillMention(prompt, tokenIndex) {
|
|
23
|
+
return nearestLitcodexSkillNameBefore(prompt, tokenIndex) === "start-work";
|
|
24
|
+
}
|
|
25
|
+
function nearestLitcodexSkillNameBefore(prompt, tokenIndex) {
|
|
26
|
+
let scopedSkillName = null;
|
|
17
27
|
for (const match of prompt.slice(0, tokenIndex).matchAll(CODEX_SKILL_MENTION_PATTERN)) {
|
|
18
28
|
const pluginName = match[1]?.toLowerCase();
|
|
19
29
|
const skillName = match[2]?.toLowerCase();
|
|
20
30
|
if (pluginName !== LITCODEX_PLUGIN_NAME || skillName === undefined) {
|
|
21
31
|
continue;
|
|
22
32
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
scopedToken = token;
|
|
33
|
+
if (tokenForSkillName(skillName) !== null) {
|
|
34
|
+
scopedSkillName = skillName;
|
|
26
35
|
}
|
|
27
36
|
}
|
|
28
|
-
return
|
|
37
|
+
return scopedSkillName;
|
|
29
38
|
}
|
|
30
39
|
function tokenForSkillName(skillName) {
|
|
31
40
|
switch (skillName) {
|
|
@@ -41,6 +50,8 @@ function tokenForSkillName(skillName) {
|
|
|
41
50
|
return "litresearch";
|
|
42
51
|
case "lit-recap":
|
|
43
52
|
return "lit-recap";
|
|
53
|
+
case "init-deep":
|
|
54
|
+
return "init-deep";
|
|
44
55
|
case "start-work":
|
|
45
56
|
return "start-work";
|
|
46
57
|
case "review-work":
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// appends a state_recovered ledger line + throws a typed recoverable error (NEVER auto-recreate,
|
|
8
8
|
// NEVER discard goals). Serializes same-process mutations per state dir. It MUST NEVER read,
|
|
9
9
|
// write, create, or reference the legacy runtime dir — runtime state lives ONLY under `.litcodex/lit-loop`.
|
|
10
|
+
import { createHash } from "node:crypto";
|
|
10
11
|
import { appendFile, mkdir, open, readFile, rename, stat, unlink, writeFile } from "node:fs/promises";
|
|
11
12
|
import { isAbsolute, join } from "node:path";
|
|
12
13
|
import { redactSecretsInValue } from "./redaction.js";
|
|
@@ -328,23 +329,53 @@ function compactIso() {
|
|
|
328
329
|
.replace(/[-:]/g, "")
|
|
329
330
|
.replace(/\.\d{3}Z$/, "Z");
|
|
330
331
|
}
|
|
332
|
+
function sha256(text) {
|
|
333
|
+
return createHash("sha256").update(text, "utf8").digest("hex");
|
|
334
|
+
}
|
|
335
|
+
async function readReusableRecovery(repoRoot, dir, reportPath, rawHash, byteLength) {
|
|
336
|
+
try {
|
|
337
|
+
const report = JSON.parse(await readFile(reportPath, "utf8"));
|
|
338
|
+
if (report.file !== LIT_LOOP_GOALS ||
|
|
339
|
+
report.byteLength !== byteLength ||
|
|
340
|
+
report.sha256 !== rawHash ||
|
|
341
|
+
typeof report.backup !== "string") {
|
|
342
|
+
return undefined;
|
|
343
|
+
}
|
|
344
|
+
const backupPath = join(repoRoot, report.backup);
|
|
345
|
+
if (!backupPath.startsWith(`${dir}/`))
|
|
346
|
+
return undefined;
|
|
347
|
+
await stat(backupPath);
|
|
348
|
+
return { backupPath, report: report };
|
|
349
|
+
}
|
|
350
|
+
catch {
|
|
351
|
+
return undefined;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
331
354
|
async function recoverCorrupt(repoRoot, scope, raw, cause) {
|
|
332
355
|
const dir = litLoopDir(repoRoot, scope);
|
|
333
356
|
const goalsPath = litLoopGoalsPath(repoRoot, scope);
|
|
334
357
|
const reason = cause instanceof Error ? cause.message : String(cause);
|
|
335
|
-
const backupPath = `${goalsPath}.corrupt-${compactIso()}.bak`;
|
|
336
358
|
const reportPath = join(dir, "state-recovery.json");
|
|
359
|
+
const byteLength = Buffer.byteLength(raw, "utf8");
|
|
360
|
+
const rawHash = sha256(raw);
|
|
361
|
+
const reusable = await readReusableRecovery(repoRoot, dir, reportPath, rawHash, byteLength);
|
|
362
|
+
const backupPath = reusable?.backupPath ?? `${goalsPath}.corrupt-${compactIso()}.bak`;
|
|
337
363
|
// 1. Preserve the exact bad bytes for forensics — never delete the user's data.
|
|
338
|
-
|
|
364
|
+
if (reusable === undefined) {
|
|
365
|
+
await writeFile(backupPath, raw, "utf8").catch(() => undefined);
|
|
366
|
+
}
|
|
339
367
|
// 2. Machine-readable recovery report.
|
|
340
|
-
const report = {
|
|
368
|
+
const report = reusable?.report ?? {
|
|
341
369
|
at: iso(),
|
|
342
370
|
file: LIT_LOOP_GOALS,
|
|
343
371
|
backup: repoRelative(backupPath, repoRoot),
|
|
344
372
|
reason,
|
|
345
|
-
byteLength
|
|
373
|
+
byteLength,
|
|
374
|
+
sha256: rawHash,
|
|
346
375
|
};
|
|
347
|
-
|
|
376
|
+
if (reusable === undefined) {
|
|
377
|
+
await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`, "utf8").catch(() => undefined);
|
|
378
|
+
}
|
|
348
379
|
// 3. Best-effort ledger line (recovery does not depend on the ledger succeeding).
|
|
349
380
|
await appendLedger(repoRoot, {
|
|
350
381
|
at: iso(),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** The accepted bounded lit-family tokens, longest-first (ordering is load-bearing — see below). */
|
|
2
|
-
export type LitTriggerToken = "hyperplan" | "start-work" | "review-work" | "litresearch" | "lit-recap" | "lit-loop" | "lit-plan" | "litcodex" | "litgoal" | "litwork" | "lit";
|
|
2
|
+
export type LitTriggerToken = "hyperplan" | "init-deep" | "start-work" | "review-work" | "litresearch" | "lit-recap" | "lit-loop" | "lit-plan" | "litcodex" | "litgoal" | "litwork" | "lit";
|
|
3
3
|
/**
|
|
4
4
|
* Frozen tuple of the accepted tokens in match-priority (longest-first) order.
|
|
5
5
|
* Exported so tests and the mode router (modes.ts) can enumerate without re-deriving.
|
|
@@ -8,13 +8,14 @@ export declare const LIT_TRIGGER_TOKENS: readonly LitTriggerToken[];
|
|
|
8
8
|
/**
|
|
9
9
|
* Bounded token regex kept as a non-global/non-sticky contract fixture for tests and low-level
|
|
10
10
|
* consumers. Hook activation semantics MUST go through `matchLitTrigger()`, which additionally masks
|
|
11
|
-
* Markdown code, ignores slash-command/path tokens
|
|
11
|
+
* Markdown code, ignores slash-command/path tokens except `/litresearch`, and expands natural
|
|
12
|
+
* phrases after bare `lit`.
|
|
12
13
|
* Boundaries: a token must be preceded by start-of-string OR a non `[letter|number|_]` code point,
|
|
13
14
|
* and must NOT be followed by `[letter|number|_|-]`.
|
|
14
15
|
*
|
|
15
16
|
* MUST NOT carry the /g or /y flag: a global regex retains `lastIndex` between calls and would
|
|
16
17
|
* make `.test()` return alternating results for the same input. Longest-first alternation
|
|
17
|
-
* (`hyperplan|review-work|litresearch|lit-recap|lit-loop|lit-plan|litcodex|litrecap|litgoal|litwork|recap|리캡|lit`)
|
|
18
|
+
* (`hyperplan|init-deep|start-work|review-work|litresearch|lit-recap|lit-loop|lit-plan|litcodex|litrecap|litgoal|litwork|recap|리캡|lit`)
|
|
18
19
|
* guarantees a longer family token wins over a bare `lit` at the same start; the trailing-`-`
|
|
19
20
|
* lookahead means `lit work` (space) is a bare `lit` while `litwork` (glued) is the work mode.
|
|
20
21
|
* The `litrecap` / `recap` / `리캡` alternatives are ROUTING ALIASES normalized to the canonical
|