litcodex-ai 0.3.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/LICENSE +21 -0
- package/README.md +62 -0
- package/bin/litcodex.js +12 -0
- package/dist/cli.d.ts +23 -0
- package/dist/cli.js +183 -0
- package/dist/config-migration/backup.d.ts +2 -0
- package/dist/config-migration/backup.js +42 -0
- package/dist/config-migration/catalog.d.ts +22 -0
- package/dist/config-migration/catalog.js +99 -0
- package/dist/config-migration/cli.d.ts +14 -0
- package/dist/config-migration/cli.js +85 -0
- package/dist/config-migration/config-paths.d.ts +4 -0
- package/dist/config-migration/config-paths.js +64 -0
- package/dist/config-migration/errors.d.ts +11 -0
- package/dist/config-migration/errors.js +28 -0
- package/dist/config-migration/index.d.ts +44 -0
- package/dist/config-migration/index.js +210 -0
- package/dist/config-migration/multi-agent-v2-guard.d.ts +2 -0
- package/dist/config-migration/multi-agent-v2-guard.js +106 -0
- package/dist/config-migration/root-settings.d.ts +6 -0
- package/dist/config-migration/root-settings.js +104 -0
- package/dist/config-migration/state.d.ts +16 -0
- package/dist/config-migration/state.js +40 -0
- package/dist/config-migration/toml-shape.d.ts +8 -0
- package/dist/config-migration/toml-shape.js +107 -0
- package/dist/install/codex.d.ts +34 -0
- package/dist/install/codex.js +94 -0
- package/dist/install/doctor.d.ts +12 -0
- package/dist/install/doctor.js +83 -0
- package/dist/install/errors.d.ts +19 -0
- package/dist/install/errors.js +43 -0
- package/dist/install/execute.d.ts +39 -0
- package/dist/install/execute.js +193 -0
- package/dist/install/index.d.ts +19 -0
- package/dist/install/index.js +193 -0
- package/dist/install/marketplace.d.ts +5 -0
- package/dist/install/marketplace.js +10 -0
- package/dist/install/plan.d.ts +3 -0
- package/dist/install/plan.js +54 -0
- package/dist/install/render-plan.d.ts +3 -0
- package/dist/install/render-plan.js +10 -0
- package/dist/install/types.d.ts +45 -0
- package/dist/install/types.js +5 -0
- package/model-catalog.json +31 -0
- package/node_modules/@litcodex/lit-loop/CHANGELOG.md +19 -0
- package/node_modules/@litcodex/lit-loop/LICENSE +21 -0
- package/node_modules/@litcodex/lit-loop/NOTICE +8 -0
- package/node_modules/@litcodex/lit-loop/README.md +37 -0
- package/node_modules/@litcodex/lit-loop/agents/litcodex-explorer.toml +75 -0
- package/node_modules/@litcodex/lit-loop/agents/litcodex-librarian.toml +98 -0
- package/node_modules/@litcodex/lit-loop/agents/litcodex-litwork-reviewer.toml +21 -0
- package/node_modules/@litcodex/lit-loop/agents/litcodex-metis.toml +64 -0
- package/node_modules/@litcodex/lit-loop/agents/litcodex-momus.toml +68 -0
- package/node_modules/@litcodex/lit-loop/agents/litcodex-plan.toml +163 -0
- package/node_modules/@litcodex/lit-loop/directive.md +85 -0
- package/node_modules/@litcodex/lit-loop/directives/lit-plan.md +286 -0
- package/node_modules/@litcodex/lit-loop/directives/litgoal.md +103 -0
- package/node_modules/@litcodex/lit-loop/directives/litwork.md +363 -0
- package/node_modules/@litcodex/lit-loop/dist/_scaffold.d.ts +1 -0
- package/node_modules/@litcodex/lit-loop/dist/_scaffold.js +3 -0
- package/node_modules/@litcodex/lit-loop/dist/cli.d.ts +6 -0
- package/node_modules/@litcodex/lit-loop/dist/cli.js +44 -0
- package/node_modules/@litcodex/lit-loop/dist/codex-goal-instruction.d.ts +18 -0
- package/node_modules/@litcodex/lit-loop/dist/codex-goal-instruction.js +94 -0
- package/node_modules/@litcodex/lit-loop/dist/codex-hook.d.ts +38 -0
- package/node_modules/@litcodex/lit-loop/dist/codex-hook.js +126 -0
- package/node_modules/@litcodex/lit-loop/dist/directive.d.ts +35 -0
- package/node_modules/@litcodex/lit-loop/dist/directive.js +80 -0
- package/node_modules/@litcodex/lit-loop/dist/goal-status.d.ts +12 -0
- package/node_modules/@litcodex/lit-loop/dist/goal-status.js +25 -0
- package/node_modules/@litcodex/lit-loop/dist/guards.d.ts +73 -0
- package/node_modules/@litcodex/lit-loop/dist/guards.js +215 -0
- package/node_modules/@litcodex/lit-loop/dist/hook-cli.d.ts +17 -0
- package/node_modules/@litcodex/lit-loop/dist/hook-cli.js +94 -0
- package/node_modules/@litcodex/lit-loop/dist/loop-cli.d.ts +19 -0
- package/node_modules/@litcodex/lit-loop/dist/loop-cli.js +106 -0
- package/node_modules/@litcodex/lit-loop/dist/loop-doctor-render.d.ts +7 -0
- package/node_modules/@litcodex/lit-loop/dist/loop-doctor-render.js +39 -0
- package/node_modules/@litcodex/lit-loop/dist/loop-doctor-types.d.ts +52 -0
- package/node_modules/@litcodex/lit-loop/dist/loop-doctor-types.js +7 -0
- package/node_modules/@litcodex/lit-loop/dist/loop-doctor.d.ts +21 -0
- package/node_modules/@litcodex/lit-loop/dist/loop-doctor.js +283 -0
- package/node_modules/@litcodex/lit-loop/dist/loop-errors.d.ts +15 -0
- package/node_modules/@litcodex/lit-loop/dist/loop-errors.js +43 -0
- package/node_modules/@litcodex/lit-loop/dist/loop-handlers.d.ts +18 -0
- package/node_modules/@litcodex/lit-loop/dist/loop-handlers.js +311 -0
- package/node_modules/@litcodex/lit-loop/dist/loop-model.d.ts +51 -0
- package/node_modules/@litcodex/lit-loop/dist/loop-model.js +165 -0
- package/node_modules/@litcodex/lit-loop/dist/loop-stdout.d.ts +6 -0
- package/node_modules/@litcodex/lit-loop/dist/loop-stdout.js +11 -0
- package/node_modules/@litcodex/lit-loop/dist/loop-types.d.ts +26 -0
- package/node_modules/@litcodex/lit-loop/dist/loop-types.js +8 -0
- package/node_modules/@litcodex/lit-loop/dist/markers.d.ts +9 -0
- package/node_modules/@litcodex/lit-loop/dist/markers.js +14 -0
- package/node_modules/@litcodex/lit-loop/dist/modes.d.ts +15 -0
- package/node_modules/@litcodex/lit-loop/dist/modes.js +56 -0
- package/node_modules/@litcodex/lit-loop/dist/state-paths.d.ts +41 -0
- package/node_modules/@litcodex/lit-loop/dist/state-paths.js +111 -0
- package/node_modules/@litcodex/lit-loop/dist/state-store.d.ts +39 -0
- package/node_modules/@litcodex/lit-loop/dist/state-store.js +419 -0
- package/node_modules/@litcodex/lit-loop/dist/state-types.d.ts +90 -0
- package/node_modules/@litcodex/lit-loop/dist/state-types.js +61 -0
- package/node_modules/@litcodex/lit-loop/dist/trigger.d.ts +54 -0
- package/node_modules/@litcodex/lit-loop/dist/trigger.js +75 -0
- package/node_modules/@litcodex/lit-loop/package.json +27 -0
- package/package.json +30 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// src/loop-errors.ts — M09/T14 CLI error model (A3 C7: branch on `err.code`, NOT instanceof).
|
|
2
|
+
//
|
|
3
|
+
// The CLI-owned `LitLoopError` + its stable `LoopErrorCode` union, plus `exitCodeForLoop`, which
|
|
4
|
+
// maps ANY thrown error to an exit code by reading `.code` (never `instanceof`
|
|
5
|
+
// PlanMissingError/PlanCorruptError). The store's own codes (PLAN_MISSING→3, PLAN_CORRUPT→4,
|
|
6
|
+
// WRITE_FAILED→5) defer to the store-owned `exitCodeFor`. Split out of loop-cli for the LOC ceiling
|
|
7
|
+
// and to break the cli↔handlers import cycle (handlers throw these; cli renders + maps them).
|
|
8
|
+
import { exitCodeFor } from "./state-store.js";
|
|
9
|
+
/** Machine-readable CLI error. `code` is the stable token; the exit code is derived from it. */
|
|
10
|
+
export class LitLoopError extends Error {
|
|
11
|
+
constructor(message, code, details) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = "LitLoopError";
|
|
14
|
+
this.code = code;
|
|
15
|
+
if (details !== undefined) {
|
|
16
|
+
this.details = details;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Exit code for any thrown error, branching ONLY on `err.code` (A3 C7 — never `instanceof`
|
|
22
|
+
* PlanMissingError/PlanCorruptError). Store codes (PLAN_MISSING→3, PLAN_CORRUPT→4, WRITE_FAILED→5)
|
|
23
|
+
* defer to the store-owned {@link exitCodeFor}; the CLI's own codes map here.
|
|
24
|
+
*/
|
|
25
|
+
export function exitCodeForLoop(err) {
|
|
26
|
+
const code = typeof err === "object" && err !== null && "code" in err ? err.code : undefined;
|
|
27
|
+
switch (code) {
|
|
28
|
+
case "LIT_LOOP_SUBCOMMAND_UNKNOWN":
|
|
29
|
+
return 1; // unknown subcommand / usage
|
|
30
|
+
case "LIT_LOOP_ARGUMENT_MISSING":
|
|
31
|
+
case "LIT_LOOP_ARGUMENT_INVALID":
|
|
32
|
+
case "LIT_LOOP_BRIEF_REQUIRED":
|
|
33
|
+
case "LIT_LOOP_BRIEF_FILE_UNREADABLE":
|
|
34
|
+
case "LIT_LOOP_EVIDENCE_STATUS_INVALID":
|
|
35
|
+
return 2; // bad args
|
|
36
|
+
case "LIT_LOOP_GOAL_NOT_FOUND":
|
|
37
|
+
case "LIT_LOOP_CRITERION_NOT_FOUND":
|
|
38
|
+
case "LIT_LOOP_CRITERIA_NOT_ALL_PASS":
|
|
39
|
+
return 3; // not found / unresolved
|
|
40
|
+
default:
|
|
41
|
+
return exitCodeFor(err); // store codes (3/4/5) + unexpected → 1
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type resolveLoopScope } from "./state-store.js";
|
|
2
|
+
export type LoopClock = () => string;
|
|
3
|
+
export type Ctx = {
|
|
4
|
+
repoRoot: string;
|
|
5
|
+
scope: ReturnType<typeof resolveLoopScope>;
|
|
6
|
+
now: LoopClock;
|
|
7
|
+
};
|
|
8
|
+
export interface HandlerResult {
|
|
9
|
+
readonly exitCode: number;
|
|
10
|
+
readonly text: string;
|
|
11
|
+
readonly json: Record<string, unknown>;
|
|
12
|
+
}
|
|
13
|
+
export declare function hasFlag(argv: readonly string[], flag: string): boolean;
|
|
14
|
+
export declare function handleCreate(argv: readonly string[], ctx: Ctx): Promise<HandlerResult>;
|
|
15
|
+
export declare function handleStatus(ctx: Ctx): Promise<HandlerResult>;
|
|
16
|
+
export declare function handleRun(argv: readonly string[], ctx: Ctx): Promise<HandlerResult>;
|
|
17
|
+
export declare function handleCheckpoint(argv: readonly string[], ctx: Ctx): Promise<HandlerResult>;
|
|
18
|
+
export declare function handleRecordEvidence(argv: readonly string[], ctx: Ctx): Promise<HandlerResult>;
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
// src/loop-handlers.ts — M09/T14 subcommand handlers (split out of loop-cli for the LOC ceiling).
|
|
2
|
+
//
|
|
3
|
+
// The five mutating/reading handlers (create/status/run/checkpoint/record-evidence) plus their
|
|
4
|
+
// tiny arg helpers and shared plan helpers. Each is one read→mutate→writePlan→appendLedger critical
|
|
5
|
+
// section under the store's withMutationLock. All fs I/O is delegated to the M08 store via flat
|
|
6
|
+
// `./state-store.js` (A3 C9) — this module never touches node:fs. Errors are the CLI's
|
|
7
|
+
// `LitLoopError` (mapped on `.code` by loop-cli's exitCodeForLoop) or re-thrown store errors.
|
|
8
|
+
import { buildCodexGoalCheckpoint, buildCodexGoalInstruction } from "./codex-goal-instruction.js";
|
|
9
|
+
import { LitLoopError } from "./loop-errors.js";
|
|
10
|
+
import { buildRunInstruction, deriveGoalCandidates, normalizeGoalId, pickNextRunnableGoal, requireAllCriteriaPass, seedDefaultSuccessCriteria, summarizePlan, titleFromObjective, } from "./loop-model.js";
|
|
11
|
+
import { LOOP_CREATE_STDOUT } from "./loop-stdout.js";
|
|
12
|
+
import { repoRelative } from "./state-paths.js";
|
|
13
|
+
import { appendLedger, readBriefFile, readPlan, resolveLoopStateDir, withMutationLock, writeBrief, writePlan, } from "./state-store.js";
|
|
14
|
+
// ── tiny arg helpers (no I/O) ────────────────────────────────────────────────
|
|
15
|
+
export function hasFlag(argv, flag) {
|
|
16
|
+
return argv.includes(flag);
|
|
17
|
+
}
|
|
18
|
+
function readValue(argv, flag) {
|
|
19
|
+
for (let i = 0; i < argv.length; i++) {
|
|
20
|
+
const a = argv[i];
|
|
21
|
+
if (a === flag) {
|
|
22
|
+
return argv[i + 1];
|
|
23
|
+
}
|
|
24
|
+
if (a?.startsWith(`${flag}=`)) {
|
|
25
|
+
return a.slice(flag.length + 1);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
function requireValue(argv, flag) {
|
|
31
|
+
const v = readValue(argv, flag);
|
|
32
|
+
if (v === undefined || v === "") {
|
|
33
|
+
throw new LitLoopError(`Missing ${flag}.`, "LIT_LOOP_ARGUMENT_MISSING", { flag });
|
|
34
|
+
}
|
|
35
|
+
return v;
|
|
36
|
+
}
|
|
37
|
+
function requireNonEmpty(value, flag) {
|
|
38
|
+
if (value.trim() === "") {
|
|
39
|
+
throw new LitLoopError(`Missing ${flag}.`, "LIT_LOOP_ARGUMENT_MISSING", { flag });
|
|
40
|
+
}
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
function positionalText(argv) {
|
|
44
|
+
return argv.filter((a) => !a.startsWith("-")).join(" ");
|
|
45
|
+
}
|
|
46
|
+
// ── create ───────────────────────────────────────────────────────────────────
|
|
47
|
+
async function resolveBrief(argv, repoRoot) {
|
|
48
|
+
const inline = readValue(argv, "--brief");
|
|
49
|
+
if (inline !== undefined && inline.trim() !== "") {
|
|
50
|
+
return inline;
|
|
51
|
+
}
|
|
52
|
+
const briefFile = readValue(argv, "--brief-file");
|
|
53
|
+
if (briefFile !== undefined && briefFile !== "") {
|
|
54
|
+
try {
|
|
55
|
+
return await readBriefFile(repoRoot, briefFile);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
throw new LitLoopError(`Cannot read --brief-file: ${briefFile}.`, "LIT_LOOP_BRIEF_FILE_UNREADABLE", {
|
|
59
|
+
path: briefFile,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return positionalText(argv);
|
|
64
|
+
}
|
|
65
|
+
export async function handleCreate(argv, ctx) {
|
|
66
|
+
const brief = await resolveBrief(argv, ctx.repoRoot);
|
|
67
|
+
if (brief.trim() === "") {
|
|
68
|
+
throw new LitLoopError("Missing brief text.", "LIT_LOOP_BRIEF_REQUIRED");
|
|
69
|
+
}
|
|
70
|
+
const force = hasFlag(argv, "--force");
|
|
71
|
+
return withMutationLock(ctx.repoRoot, ctx.scope, async () => {
|
|
72
|
+
const existing = await readPlanOrNull(ctx);
|
|
73
|
+
if (existing !== null && !force) {
|
|
74
|
+
return planResult(existing); // idempotent no-op
|
|
75
|
+
}
|
|
76
|
+
const now = ctx.now();
|
|
77
|
+
const candidates = deriveGoalCandidates(brief);
|
|
78
|
+
const goals = candidates.map((objective, i) => ({
|
|
79
|
+
id: normalizeGoalId(i, objective),
|
|
80
|
+
title: titleFromObjective(objective),
|
|
81
|
+
objective,
|
|
82
|
+
status: "pending",
|
|
83
|
+
successCriteria: seedDefaultSuccessCriteria(objective),
|
|
84
|
+
attempt: 0,
|
|
85
|
+
createdAt: now,
|
|
86
|
+
updatedAt: now,
|
|
87
|
+
}));
|
|
88
|
+
const paths = planPaths(ctx);
|
|
89
|
+
const plan = {
|
|
90
|
+
version: 1,
|
|
91
|
+
createdAt: now,
|
|
92
|
+
updatedAt: now,
|
|
93
|
+
...paths,
|
|
94
|
+
sessionId: ctx.scope?.sessionId ?? null,
|
|
95
|
+
goals,
|
|
96
|
+
};
|
|
97
|
+
await writeBrief(ctx.repoRoot, brief, ctx.scope);
|
|
98
|
+
await writePlan(ctx.repoRoot, plan, ctx.scope);
|
|
99
|
+
await appendLedger(ctx.repoRoot, { at: now, kind: "plan_created", message: `${goals.length} goal(s) derived from brief.` }, ctx.scope);
|
|
100
|
+
return {
|
|
101
|
+
exitCode: 0,
|
|
102
|
+
text: LOOP_CREATE_STDOUT(goals.length, plan),
|
|
103
|
+
json: { ok: true, plan, summary: summarizePlan(plan) },
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
// ── status ───────────────────────────────────────────────────────────────────
|
|
108
|
+
export async function handleStatus(ctx) {
|
|
109
|
+
const plan = await readPlan(ctx.repoRoot, ctx.scope);
|
|
110
|
+
const summary = summarizePlan(plan);
|
|
111
|
+
const header = `lit-loop status: ${summary.total} goals (${summary.pending} pending, ${summary.in_progress} in progress, ${summary.complete} complete, ${summary.failed} failed, ${summary.blocked} blocked)`;
|
|
112
|
+
const lines = plan.goals.map((g) => {
|
|
113
|
+
const pass = g.successCriteria.filter((c) => c.status === "pass").length;
|
|
114
|
+
return `- ${g.id} [${g.status}] ${g.title} (criteria ${pass}/${g.successCriteria.length} pass)`;
|
|
115
|
+
});
|
|
116
|
+
return { exitCode: 0, text: `${[header, ...lines].join("\n")}\n`, json: { ok: true, plan, summary } };
|
|
117
|
+
}
|
|
118
|
+
// ── run ──────────────────────────────────────────────────────────────────────
|
|
119
|
+
export async function handleRun(argv, ctx) {
|
|
120
|
+
const retryFailed = hasFlag(argv, "--retry-failed");
|
|
121
|
+
return withMutationLock(ctx.repoRoot, ctx.scope, async () => {
|
|
122
|
+
const plan = await readPlan(ctx.repoRoot, ctx.scope);
|
|
123
|
+
const pick = pickNextRunnableGoal(plan, { retryFailed });
|
|
124
|
+
if (pick === null) {
|
|
125
|
+
return {
|
|
126
|
+
exitCode: 0,
|
|
127
|
+
text: "lit-loop: all goals complete\n",
|
|
128
|
+
json: { ok: true, done: true, summary: summarizePlan(plan), plan },
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
const now = ctx.now();
|
|
132
|
+
const goal = pick.goal;
|
|
133
|
+
if (pick.retried) {
|
|
134
|
+
goal.status = "pending";
|
|
135
|
+
await appendLedger(ctx.repoRoot, { at: now, kind: "goal_retried", goalId: goal.id }, ctx.scope);
|
|
136
|
+
}
|
|
137
|
+
goal.status = "in_progress";
|
|
138
|
+
goal.attempt += 1;
|
|
139
|
+
goal.startedAt ??= now;
|
|
140
|
+
goal.updatedAt = now;
|
|
141
|
+
plan.activeGoalId = goal.id;
|
|
142
|
+
plan.updatedAt = now;
|
|
143
|
+
await writePlan(ctx.repoRoot, plan, ctx.scope);
|
|
144
|
+
await appendLedger(ctx.repoRoot, {
|
|
145
|
+
at: now,
|
|
146
|
+
kind: pick.resumed ? "goal_resumed" : "goal_started",
|
|
147
|
+
goalId: goal.id,
|
|
148
|
+
message: `Attempt ${goal.attempt}`,
|
|
149
|
+
}, ctx.scope);
|
|
150
|
+
const runText = buildRunInstruction(plan, goal);
|
|
151
|
+
const goalInstruction = buildCodexGoalInstruction({ plan, goal });
|
|
152
|
+
const text = `${runText}\n${goalInstruction.text}\n`;
|
|
153
|
+
return {
|
|
154
|
+
exitCode: 0,
|
|
155
|
+
text,
|
|
156
|
+
json: {
|
|
157
|
+
ok: true,
|
|
158
|
+
done: false,
|
|
159
|
+
resumed: pick.resumed,
|
|
160
|
+
goal,
|
|
161
|
+
instruction: { text },
|
|
162
|
+
codexGoal: goalInstruction.json,
|
|
163
|
+
plan,
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
// ── checkpoint ───────────────────────────────────────────────────────────────
|
|
169
|
+
const CHECKPOINT_STATUSES = new Set(["complete", "failed", "blocked"]);
|
|
170
|
+
export async function handleCheckpoint(argv, ctx) {
|
|
171
|
+
const goalId = requireValue(argv, "--goal-id");
|
|
172
|
+
const statusRaw = requireValue(argv, "--status");
|
|
173
|
+
if (!CHECKPOINT_STATUSES.has(statusRaw)) {
|
|
174
|
+
throw new LitLoopError(`Invalid --status: ${statusRaw} (use complete|failed|blocked).`, "LIT_LOOP_ARGUMENT_INVALID", {
|
|
175
|
+
status: statusRaw,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
const status = statusRaw;
|
|
179
|
+
const evidence = requireNonEmpty(requireValue(argv, "--evidence"), "--evidence");
|
|
180
|
+
return withMutationLock(ctx.repoRoot, ctx.scope, async () => {
|
|
181
|
+
const plan = await readPlan(ctx.repoRoot, ctx.scope);
|
|
182
|
+
const goal = findGoal(plan, goalId);
|
|
183
|
+
if (status === "complete") {
|
|
184
|
+
const unresolved = requireAllCriteriaPass(goal);
|
|
185
|
+
if (unresolved.length > 0) {
|
|
186
|
+
throw new LitLoopError(`Goal ${goalId} has unresolved criteria.`, "LIT_LOOP_CRITERIA_NOT_ALL_PASS", {
|
|
187
|
+
goalId,
|
|
188
|
+
unresolved,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const now = ctx.now();
|
|
193
|
+
goal.status = status;
|
|
194
|
+
goal.evidence = evidence;
|
|
195
|
+
goal.updatedAt = now;
|
|
196
|
+
if (status === "complete") {
|
|
197
|
+
goal.completedAt = now;
|
|
198
|
+
delete goal.failedAt;
|
|
199
|
+
delete goal.failureReason;
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
goal.failedAt = now;
|
|
203
|
+
goal.failureReason = evidence;
|
|
204
|
+
}
|
|
205
|
+
if (plan.activeGoalId === goalId) {
|
|
206
|
+
delete plan.activeGoalId;
|
|
207
|
+
}
|
|
208
|
+
plan.updatedAt = now;
|
|
209
|
+
const kind = status === "complete" ? "goal_completed" : status === "failed" ? "goal_failed" : "goal_blocked";
|
|
210
|
+
const ledgerEntry = { at: now, kind, goalId, goalStatus: status, evidence };
|
|
211
|
+
await writePlan(ctx.repoRoot, plan, ctx.scope);
|
|
212
|
+
await appendLedger(ctx.repoRoot, ledgerEntry, ctx.scope);
|
|
213
|
+
const codexCheckpoint = buildCodexGoalCheckpoint({ plan, goal, status });
|
|
214
|
+
return {
|
|
215
|
+
exitCode: 0,
|
|
216
|
+
text: `lit-loop checkpoint: ${goalId} -> ${status}\n\n${codexCheckpoint}\n`,
|
|
217
|
+
json: { ok: true, goal, ledgerEntry, codexGoal: { status }, plan, summary: summarizePlan(plan) },
|
|
218
|
+
};
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
// -- record-evidence -----------------------------------------------------------
|
|
222
|
+
const EVIDENCE_STATUSES = new Set(["pass", "fail", "blocked"]);
|
|
223
|
+
export async function handleRecordEvidence(argv, ctx) {
|
|
224
|
+
const goalId = requireValue(argv, "--goal-id");
|
|
225
|
+
const criterionId = requireValue(argv, "--criterion-id");
|
|
226
|
+
const statusRaw = requireValue(argv, "--status");
|
|
227
|
+
if (!EVIDENCE_STATUSES.has(statusRaw)) {
|
|
228
|
+
throw new LitLoopError(`Invalid --status: ${statusRaw} (use pass|fail|blocked).`, "LIT_LOOP_EVIDENCE_STATUS_INVALID", {
|
|
229
|
+
status: statusRaw,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
const status = statusRaw;
|
|
233
|
+
const evidence = requireNonEmpty(requireValue(argv, "--evidence"), "--evidence");
|
|
234
|
+
const notes = readValue(argv, "--notes");
|
|
235
|
+
return withMutationLock(ctx.repoRoot, ctx.scope, async () => {
|
|
236
|
+
const plan = await readPlan(ctx.repoRoot, ctx.scope);
|
|
237
|
+
const goal = findGoal(plan, goalId);
|
|
238
|
+
const crit = goal.successCriteria.find((c) => c.id === criterionId);
|
|
239
|
+
if (crit === undefined) {
|
|
240
|
+
throw new LitLoopError(`Unknown criterion: ${criterionId}.`, "LIT_LOOP_CRITERION_NOT_FOUND", {
|
|
241
|
+
goalId,
|
|
242
|
+
criterionId,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
const now = ctx.now();
|
|
246
|
+
const prevStatus = crit.status;
|
|
247
|
+
crit.status = status;
|
|
248
|
+
crit.capturedEvidence = evidence;
|
|
249
|
+
crit.capturedAt = now;
|
|
250
|
+
if (notes !== undefined) {
|
|
251
|
+
crit.notes = notes;
|
|
252
|
+
}
|
|
253
|
+
goal.updatedAt = now;
|
|
254
|
+
plan.updatedAt = now;
|
|
255
|
+
const kind = status === "pass" ? "evidence_captured" : status === "fail" ? "criterion_failed" : "criterion_blocked";
|
|
256
|
+
const ledgerEntry = {
|
|
257
|
+
at: now,
|
|
258
|
+
kind,
|
|
259
|
+
goalId,
|
|
260
|
+
criterionId,
|
|
261
|
+
criterionStatus: status,
|
|
262
|
+
evidence,
|
|
263
|
+
before: { status: prevStatus },
|
|
264
|
+
after: { status, capturedAt: now },
|
|
265
|
+
};
|
|
266
|
+
await writePlan(ctx.repoRoot, plan, ctx.scope);
|
|
267
|
+
await appendLedger(ctx.repoRoot, ledgerEntry, ctx.scope);
|
|
268
|
+
return {
|
|
269
|
+
exitCode: 0,
|
|
270
|
+
text: `lit-loop evidence recorded: ${goalId}/${criterionId} -> ${status}\n`,
|
|
271
|
+
json: { ok: true, goal, criterion: crit, ledgerEntry, plan, summary: summarizePlan(plan) },
|
|
272
|
+
};
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
// ── shared helpers ───────────────────────────────────────────────────────────
|
|
276
|
+
function findGoal(plan, goalId) {
|
|
277
|
+
const goal = plan.goals.find((g) => g.id === goalId);
|
|
278
|
+
if (goal === undefined) {
|
|
279
|
+
throw new LitLoopError(`Unknown lit-loop goal id: ${goalId}.`, "LIT_LOOP_GOAL_NOT_FOUND", { goalId });
|
|
280
|
+
}
|
|
281
|
+
return goal;
|
|
282
|
+
}
|
|
283
|
+
function planPaths(ctx) {
|
|
284
|
+
const dir = resolveLoopStateDir(ctx.repoRoot, ctx.scope);
|
|
285
|
+
const rel = repoRelative(dir, ctx.repoRoot);
|
|
286
|
+
return {
|
|
287
|
+
briefPath: `${rel}/brief.md`,
|
|
288
|
+
goalsPath: `${rel}/goals.json`,
|
|
289
|
+
ledgerPath: `${rel}/ledger.jsonl`,
|
|
290
|
+
evidenceDir: `${rel}/evidence`,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
async function readPlanOrNull(ctx) {
|
|
294
|
+
try {
|
|
295
|
+
return await readPlan(ctx.repoRoot, ctx.scope);
|
|
296
|
+
}
|
|
297
|
+
catch (err) {
|
|
298
|
+
const code = typeof err === "object" && err !== null && "code" in err ? err.code : undefined;
|
|
299
|
+
if (code === "LIT_LOOP_PLAN_MISSING") {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
throw err;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
function planResult(plan) {
|
|
306
|
+
return {
|
|
307
|
+
exitCode: 0,
|
|
308
|
+
text: LOOP_CREATE_STDOUT(plan.goals.length, plan),
|
|
309
|
+
json: { ok: true, plan, summary: summarizePlan(plan) },
|
|
310
|
+
};
|
|
311
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { LoopCriterion, LoopGoal, LoopPlan, PlanSummary } from "./loop-types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Derive ordered goal objective strings from a free-text brief. Bullets/numbered lines win; if
|
|
4
|
+
* none, non-heading paragraphs; if neither yields a candidate, a single fallback objective. Pure
|
|
5
|
+
* and deterministic — a single linear pass, per-line capped at 1200 chars, deduped keeping first.
|
|
6
|
+
*/
|
|
7
|
+
export declare function deriveGoalCandidates(brief: string): string[];
|
|
8
|
+
/**
|
|
9
|
+
* Build a goal id `G` + 3-digit zero-padded (index+1) + optional `-<slug>`. The slug strips every
|
|
10
|
+
* non-`[a-z0-9]` run to `-`, trims edge dashes, caps at 36 chars; an empty slug body drops the
|
|
11
|
+
* trailing dash so the id is just `G001`. Always matches `^G\d{3}(-[a-z0-9-]+)?$`.
|
|
12
|
+
*/
|
|
13
|
+
export declare function normalizeGoalId(index: number, objective: string): string;
|
|
14
|
+
/** First non-empty line of the objective, truncated to 69 + "..." when longer than 72 chars. */
|
|
15
|
+
export declare function titleFromObjective(objective: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Seed exactly C001(happy)/C002(edge)/C003(regression), each pending with no captured evidence.
|
|
18
|
+
* The objective's first 80 chars form the human-readable subject in the happy-path scenario.
|
|
19
|
+
*/
|
|
20
|
+
export declare function seedDefaultSuccessCriteria(objective: string): LoopCriterion[];
|
|
21
|
+
/** A scheduler pick: the selected goal plus whether it was a resume or a retry re-pick. */
|
|
22
|
+
export interface RunnablePick {
|
|
23
|
+
goal: LoopGoal;
|
|
24
|
+
resumed: boolean;
|
|
25
|
+
retried: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Select the next goal to run: first `in_progress` (resume), else first `pending`, else — only
|
|
29
|
+
* when `retryFailed` — the first `failed` (retry re-pick), else null. Pure: returns the goal
|
|
30
|
+
* reference; the caller (R4) applies the attempt/status mutations.
|
|
31
|
+
*/
|
|
32
|
+
export declare function pickNextRunnableGoal(plan: LoopPlan, opts: {
|
|
33
|
+
retryFailed: boolean;
|
|
34
|
+
}): RunnablePick | null;
|
|
35
|
+
/**
|
|
36
|
+
* Returns the list of `{id,status}` criteria blocking completion. A goal completes only when it
|
|
37
|
+
* has >=1 criterion and EVERY criterion is `pass`; an empty list FAILS (returns a sentinel entry).
|
|
38
|
+
*/
|
|
39
|
+
export declare function requireAllCriteriaPass(goal: LoopGoal): Array<{
|
|
40
|
+
id: string;
|
|
41
|
+
status: string;
|
|
42
|
+
}>;
|
|
43
|
+
/** Roll up goal-status counts and the criteria pass/pending/fail/blocked totals. Never persisted. */
|
|
44
|
+
export declare function summarizePlan(plan: LoopPlan): PlanSummary;
|
|
45
|
+
/**
|
|
46
|
+
* The deterministic, marker-free, legacy-token-free run handoff block. No `<lit-loop-mode>`
|
|
47
|
+
* marker (that is the M15 directive, not the CLI instruction) and no marketing copy — only the
|
|
48
|
+
* plan/ledger paths, the goal, its objective, the seeded criteria, and the LitCodex-native
|
|
49
|
+
* next-action commands a caller runs to record evidence and checkpoint.
|
|
50
|
+
*/
|
|
51
|
+
export declare function buildRunInstruction(plan: LoopPlan, goal: LoopGoal): string;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// src/loop-model.ts — M09/T14 PURE domain logic (A3 C7, S09 §5 model rules).
|
|
2
|
+
//
|
|
3
|
+
// Goal derivation from a brief, success-criteria seeding, next-runnable-goal selection, plan
|
|
4
|
+
// summary, the all-criteria-pass gate, and the deterministic run-handoff text. Operates on
|
|
5
|
+
// in-memory plan objects only. Imports ONLY state-types (types) — NO node:fs, NO store, NO I/O,
|
|
6
|
+
// no `process`, no clock. Every output is fully determined by its arguments.
|
|
7
|
+
const FALLBACK_OBJECTIVE = "Complete the requested project objective.";
|
|
8
|
+
const MAX_OBJECTIVE = 1200;
|
|
9
|
+
const MAX_TITLE = 72;
|
|
10
|
+
const BULLET_RE = /^\s*(?:[-*+]\s+|\d+[.)]\s+)/;
|
|
11
|
+
/** Strip a leading bullet/numbered marker and surrounding whitespace from one line. */
|
|
12
|
+
function cleanLine(line) {
|
|
13
|
+
return line.replace(BULLET_RE, "").trim();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Derive ordered goal objective strings from a free-text brief. Bullets/numbered lines win; if
|
|
17
|
+
* none, non-heading paragraphs; if neither yields a candidate, a single fallback objective. Pure
|
|
18
|
+
* and deterministic — a single linear pass, per-line capped at 1200 chars, deduped keeping first.
|
|
19
|
+
*/
|
|
20
|
+
export function deriveGoalCandidates(brief) {
|
|
21
|
+
const lines = brief.split(/\r?\n/);
|
|
22
|
+
const bulletLines = lines.filter((l) => BULLET_RE.test(l));
|
|
23
|
+
let raw;
|
|
24
|
+
if (bulletLines.length > 0) {
|
|
25
|
+
raw = bulletLines.map(cleanLine);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
raw = brief
|
|
29
|
+
.split(/\n\s*\n/)
|
|
30
|
+
.map((p) => p.trim())
|
|
31
|
+
.filter((p) => p.length > 0 && !p.startsWith("#"));
|
|
32
|
+
}
|
|
33
|
+
const seen = new Set();
|
|
34
|
+
const candidates = [];
|
|
35
|
+
for (const value of raw) {
|
|
36
|
+
if (value.length === 0 || value.length > MAX_OBJECTIVE) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (seen.has(value)) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
seen.add(value);
|
|
43
|
+
candidates.push(value);
|
|
44
|
+
}
|
|
45
|
+
return candidates.length > 0 ? candidates : [FALLBACK_OBJECTIVE];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Build a goal id `G` + 3-digit zero-padded (index+1) + optional `-<slug>`. The slug strips every
|
|
49
|
+
* non-`[a-z0-9]` run to `-`, trims edge dashes, caps at 36 chars; an empty slug body drops the
|
|
50
|
+
* trailing dash so the id is just `G001`. Always matches `^G\d{3}(-[a-z0-9-]+)?$`.
|
|
51
|
+
*/
|
|
52
|
+
export function normalizeGoalId(index, objective) {
|
|
53
|
+
const num = String(index + 1).padStart(3, "0");
|
|
54
|
+
const slugBody = objective
|
|
55
|
+
.toLowerCase()
|
|
56
|
+
.replace(/[^a-z0-9]+/gu, "-")
|
|
57
|
+
.replace(/^-+|-+$/g, "")
|
|
58
|
+
.slice(0, 36)
|
|
59
|
+
.replace(/-+$/g, "");
|
|
60
|
+
return slugBody.length > 0 ? `G${num}-${slugBody}` : `G${num}`;
|
|
61
|
+
}
|
|
62
|
+
/** First non-empty line of the objective, truncated to 69 + "..." when longer than 72 chars. */
|
|
63
|
+
export function titleFromObjective(objective) {
|
|
64
|
+
const firstLine = objective
|
|
65
|
+
.split(/\r?\n/)
|
|
66
|
+
.find((l) => l.trim().length > 0)
|
|
67
|
+
?.trim() ?? "";
|
|
68
|
+
return firstLine.length > MAX_TITLE ? `${firstLine.slice(0, MAX_TITLE - 3)}...` : firstLine;
|
|
69
|
+
}
|
|
70
|
+
function truncate(value, max) {
|
|
71
|
+
return value.length > max ? value.slice(0, max) : value;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Seed exactly C001(happy)/C002(edge)/C003(regression), each pending with no captured evidence.
|
|
75
|
+
* The objective's first 80 chars form the human-readable subject in the happy-path scenario.
|
|
76
|
+
*/
|
|
77
|
+
export function seedDefaultSuccessCriteria(objective) {
|
|
78
|
+
const subject = truncate(titleFromObjective(objective) || objective, 80);
|
|
79
|
+
const seed = (id, userModel, scenario, expectedEvidence) => ({
|
|
80
|
+
id,
|
|
81
|
+
scenario,
|
|
82
|
+
userModel,
|
|
83
|
+
expectedEvidence,
|
|
84
|
+
capturedEvidence: null,
|
|
85
|
+
status: "pending",
|
|
86
|
+
});
|
|
87
|
+
return [
|
|
88
|
+
seed("C001", "happy", `happy path for: ${subject}`, "Run the happy-path scenario and capture passing output via record-evidence."),
|
|
89
|
+
seed("C002", "edge", "edge case (boundary/empty/malformed)", "Exercise a boundary/empty/malformed input and capture the result."),
|
|
90
|
+
seed("C003", "regression", "regression: adjacent surface still works", "Verify an adjacent surface is unbroken and capture proof."),
|
|
91
|
+
];
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Select the next goal to run: first `in_progress` (resume), else first `pending`, else — only
|
|
95
|
+
* when `retryFailed` — the first `failed` (retry re-pick), else null. Pure: returns the goal
|
|
96
|
+
* reference; the caller (R4) applies the attempt/status mutations.
|
|
97
|
+
*/
|
|
98
|
+
export function pickNextRunnableGoal(plan, opts) {
|
|
99
|
+
const inProgress = plan.goals.find((g) => g.status === "in_progress");
|
|
100
|
+
if (inProgress) {
|
|
101
|
+
return { goal: inProgress, resumed: true, retried: false };
|
|
102
|
+
}
|
|
103
|
+
const pending = plan.goals.find((g) => g.status === "pending");
|
|
104
|
+
if (pending) {
|
|
105
|
+
return { goal: pending, resumed: false, retried: false };
|
|
106
|
+
}
|
|
107
|
+
if (opts.retryFailed) {
|
|
108
|
+
const failed = plan.goals.find((g) => g.status === "failed");
|
|
109
|
+
if (failed) {
|
|
110
|
+
return { goal: failed, resumed: false, retried: true };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Returns the list of `{id,status}` criteria blocking completion. A goal completes only when it
|
|
117
|
+
* has >=1 criterion and EVERY criterion is `pass`; an empty list FAILS (returns a sentinel entry).
|
|
118
|
+
*/
|
|
119
|
+
export function requireAllCriteriaPass(goal) {
|
|
120
|
+
if (goal.successCriteria.length === 0) {
|
|
121
|
+
return [{ id: "(none)", status: "missing" }];
|
|
122
|
+
}
|
|
123
|
+
return goal.successCriteria.filter((c) => c.status !== "pass").map((c) => ({ id: c.id, status: c.status }));
|
|
124
|
+
}
|
|
125
|
+
/** Roll up goal-status counts and the criteria pass/pending/fail/blocked totals. Never persisted. */
|
|
126
|
+
export function summarizePlan(plan) {
|
|
127
|
+
const summary = {
|
|
128
|
+
total: plan.goals.length,
|
|
129
|
+
pending: 0,
|
|
130
|
+
in_progress: 0,
|
|
131
|
+
complete: 0,
|
|
132
|
+
failed: 0,
|
|
133
|
+
blocked: 0,
|
|
134
|
+
criteria: { total: 0, pass: 0, pending: 0, fail: 0, blocked: 0 },
|
|
135
|
+
};
|
|
136
|
+
for (const goal of plan.goals) {
|
|
137
|
+
summary[goal.status] += 1;
|
|
138
|
+
for (const crit of goal.successCriteria) {
|
|
139
|
+
summary.criteria.total += 1;
|
|
140
|
+
summary.criteria[crit.status] += 1;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return summary;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* The deterministic, marker-free, legacy-token-free run handoff block. No `<lit-loop-mode>`
|
|
147
|
+
* marker (that is the M15 directive, not the CLI instruction) and no marketing copy — only the
|
|
148
|
+
* plan/ledger paths, the goal, its objective, the seeded criteria, and the LitCodex-native
|
|
149
|
+
* next-action commands a caller runs to record evidence and checkpoint.
|
|
150
|
+
*/
|
|
151
|
+
export function buildRunInstruction(plan, goal) {
|
|
152
|
+
const lines = [
|
|
153
|
+
"lit-loop active-goal handoff",
|
|
154
|
+
`Plan: ${plan.goalsPath}`,
|
|
155
|
+
`Ledger: ${plan.ledgerPath}`,
|
|
156
|
+
`Goal: ${goal.id} — ${goal.title}`,
|
|
157
|
+
`Objective: ${goal.objective}`,
|
|
158
|
+
"Success criteria:",
|
|
159
|
+
];
|
|
160
|
+
for (const c of goal.successCriteria) {
|
|
161
|
+
lines.push(`- [${c.id}] (${c.userModel}) ${c.scenario} — expect: ${c.expectedEvidence} — status: ${c.status}`);
|
|
162
|
+
}
|
|
163
|
+
lines.push("Next actions:", "1. Do the work for this goal.", `2. For each criterion: litcodex loop record-evidence --goal-id ${goal.id} --criterion-id <Cxxx> --status pass --evidence "<proof>"`, `3. Once all criteria pass: litcodex loop checkpoint --goal-id ${goal.id} --status complete --evidence "<summary>"`);
|
|
164
|
+
return `${lines.join("\n")}\n`;
|
|
165
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// src/loop-stdout.ts — M09/T14 `loop create` stdout contract (A3 C14). M20 imports this const.
|
|
2
|
+
//
|
|
3
|
+
// The single owner of the exact `loop create` stdout block: 4 lines, trailing `\n`. Exported as a
|
|
4
|
+
// named const so M20 (path-robustness) imports it rather than hardcoding the literal. Split out of
|
|
5
|
+
// loop-cli so both loop-cli (the dispatcher) and loop-handlers (the create handler) can import it
|
|
6
|
+
// without an import cycle.
|
|
7
|
+
/** The exact `loop create` stdout block (A3 C14): 4 lines, trailing `\n`. */
|
|
8
|
+
export const LOOP_CREATE_STDOUT = (goalCount, paths) => `lit-loop plan created: ${goalCount} goal(s)\n` +
|
|
9
|
+
`brief: ${paths.briefPath}\n` +
|
|
10
|
+
`goals: ${paths.goalsPath}\n` +
|
|
11
|
+
`ledger: ${paths.ledgerPath}\n`;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export type { LitLoopCriterion, LitLoopCriterionStatus, LitLoopGoal, LitLoopGoalStatus, LitLoopLedgerEntry, LitLoopLedgerEventKind, LitLoopLedgerInput, LitLoopPlan, LitLoopUserModel, } from "./state-types.js";
|
|
2
|
+
export { LitLoopStateError } from "./state-types.js";
|
|
3
|
+
import type { LitLoopCriterion, LitLoopCriterionStatus, LitLoopGoal, LitLoopGoalStatus, LitLoopLedgerEntry, LitLoopLedgerEventKind, LitLoopPlan, LitLoopUserModel } from "./state-types.js";
|
|
4
|
+
export type LoopPlan = LitLoopPlan;
|
|
5
|
+
export type LoopGoal = LitLoopGoal;
|
|
6
|
+
export type LoopCriterion = LitLoopCriterion;
|
|
7
|
+
export type LoopLedgerEntry = LitLoopLedgerEntry;
|
|
8
|
+
export type LoopLedgerKind = LitLoopLedgerEventKind;
|
|
9
|
+
export type LoopGoalStatus = LitLoopGoalStatus;
|
|
10
|
+
export type LoopCriterionStatus = LitLoopCriterionStatus;
|
|
11
|
+
export type LoopUserModel = LitLoopUserModel;
|
|
12
|
+
export interface PlanSummary {
|
|
13
|
+
total: number;
|
|
14
|
+
pending: number;
|
|
15
|
+
in_progress: number;
|
|
16
|
+
complete: number;
|
|
17
|
+
failed: number;
|
|
18
|
+
blocked: number;
|
|
19
|
+
criteria: {
|
|
20
|
+
total: number;
|
|
21
|
+
pass: number;
|
|
22
|
+
pending: number;
|
|
23
|
+
fail: number;
|
|
24
|
+
blocked: number;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// src/loop-types.ts — M09/T14 re-export / type-alias barrel (A3 C7, C9 flat layout).
|
|
2
|
+
//
|
|
3
|
+
// `state-types.ts` (M08) is the SINGLE schema source. This module declares NO independent
|
|
4
|
+
// plan/goal/criterion/ledger shapes — it only re-exports the store types under M09's spelling
|
|
5
|
+
// (`LoopPlan = LitLoopPlan`, etc.) plus the two pure VIEW types M09 computes and M11's doctor
|
|
6
|
+
// imports (`PlanSummary`, `LoopGoalStatus`). Flat sibling import `./state-types.js` (NOT
|
|
7
|
+
// `../state/...`). Types only; ZERO runtime / I/O.
|
|
8
|
+
export { LitLoopStateError } from "./state-types.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const LIT_LOOP_DIRECTIVE_MARKER: "<lit-loop-mode>";
|
|
2
|
+
export declare const LIT_LOOP_DIRECTIVE_CLOSE: "</lit-loop-mode>";
|
|
3
|
+
export declare const LITWORK_DIRECTIVE_MARKER: "<litwork-mode>";
|
|
4
|
+
export declare const LITWORK_DIRECTIVE_CLOSE: "</litwork-mode>";
|
|
5
|
+
export declare const LIT_PLAN_DIRECTIVE_MARKER: "<lit-plan-mode>";
|
|
6
|
+
export declare const LIT_PLAN_DIRECTIVE_CLOSE: "</lit-plan-mode>";
|
|
7
|
+
export declare const LITGOAL_DIRECTIVE_MARKER: "<litgoal-mode>";
|
|
8
|
+
export declare const LITGOAL_DIRECTIVE_CLOSE: "</litgoal-mode>";
|
|
9
|
+
export type LitLoopDirectiveMarker = typeof LIT_LOOP_DIRECTIVE_MARKER;
|