@njeybe/nativ 2.4.1 → 2.5.1
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/CHANGELOG.md +26 -0
- package/README.md +26 -0
- package/dist/commands/db/db-formatter.d.ts +5 -0
- package/dist/commands/db/db-formatter.js +116 -0
- package/dist/commands/db/db-introspect.d.ts +11 -0
- package/dist/commands/db/db-introspect.js +84 -0
- package/dist/commands/db.js +4 -206
- package/dist/commands/profile.d.ts +3 -0
- package/dist/commands/profile.js +39 -0
- package/dist/commands/task/task-add.d.ts +11 -0
- package/dist/commands/task/task-add.js +169 -0
- package/dist/commands/task/task-common.d.ts +57 -0
- package/dist/commands/task/task-common.js +45 -0
- package/dist/commands/task/task-escalate.d.ts +7 -0
- package/dist/commands/task/task-escalate.js +119 -0
- package/dist/commands/task/task-lifecycle.d.ts +5 -0
- package/dist/commands/task/task-lifecycle.js +252 -0
- package/dist/commands/task/task-query.d.ts +10 -0
- package/dist/commands/task/task-query.js +269 -0
- package/dist/commands/task.d.ts +9 -87
- package/dist/commands/task.js +8 -850
- package/dist/commands/triage/triage-store.d.ts +56 -0
- package/dist/commands/triage/triage-store.js +108 -0
- package/dist/commands/triage/triage-ui.d.ts +14 -0
- package/dist/commands/triage/triage-ui.js +104 -0
- package/dist/commands/triage.d.ts +3 -52
- package/dist/commands/triage.js +8 -216
- package/dist/core/enforcement.d.ts +1 -0
- package/dist/core/enforcement.js +19 -0
- package/dist/core/human-gate.d.ts +2 -1
- package/dist/core/human-gate.js +9 -2
- package/dist/core/profiles.d.ts +14 -0
- package/dist/core/profiles.js +82 -0
- package/dist/core/setup/config-merger.d.ts +59 -0
- package/dist/core/setup/config-merger.js +107 -0
- package/dist/core/setup/session-context.d.ts +4 -0
- package/dist/core/setup/session-context.js +65 -0
- package/dist/core/setup/setup-planner.d.ts +14 -0
- package/dist/core/setup/setup-planner.js +180 -0
- package/dist/core/setup/template-planner.d.ts +8 -0
- package/dist/core/setup/template-planner.js +32 -0
- package/dist/core/setup/types.d.ts +65 -0
- package/dist/core/setup/types.js +55 -0
- package/dist/core/setup-assets.d.ts +8 -119
- package/dist/core/setup-assets.js +7 -437
- package/dist/core/tier1-liaison.d.ts +2 -0
- package/dist/core/tier1-liaison.js +64 -3
- package/dist/core/verifier/executor.d.ts +5 -0
- package/dist/core/verifier/executor.js +62 -0
- package/dist/core/verifier/phases.d.ts +5 -0
- package/dist/core/verifier/phases.js +71 -0
- package/dist/core/verifier/task-verifier.d.ts +37 -0
- package/dist/core/verifier/task-verifier.js +233 -0
- package/dist/core/verifier/types.d.ts +51 -0
- package/dist/core/verifier/types.js +1 -0
- package/dist/core/verifier.d.ts +5 -93
- package/dist/core/verifier.js +5 -359
- package/dist/db/introspector.js +59 -59
- package/dist/governor/evaluator.d.ts +4 -64
- package/dist/governor/evaluator.js +21 -686
- package/dist/governor/patch-applier.d.ts +8 -0
- package/dist/governor/patch-applier.js +121 -0
- package/dist/governor/self-healing.d.ts +24 -0
- package/dist/governor/self-healing.js +351 -0
- package/dist/governor/test-integrity.d.ts +41 -0
- package/dist/governor/test-integrity.js +237 -0
- package/dist/index.js +8 -0
- package/dist/mcp/capture.d.ts +4 -0
- package/dist/mcp/capture.js +33 -0
- package/dist/mcp/resources.d.ts +39 -0
- package/dist/mcp/resources.js +44 -0
- package/dist/mcp/server.d.ts +11 -0
- package/dist/mcp/server.js +14 -89
- package/dist/mcp/tools.d.ts +1 -0
- package/dist/mcp/tools.js +1 -0
- package/dist/runner/agent-supervisor.d.ts +8 -302
- package/dist/runner/agent-supervisor.js +41 -1030
- package/dist/runner/cli-config.d.ts +84 -0
- package/dist/runner/cli-config.js +215 -0
- package/dist/runner/native-driver.d.ts +18 -0
- package/dist/runner/native-driver.js +208 -0
- package/dist/runner/native-engine.d.ts +27 -0
- package/dist/runner/native-engine.js +184 -0
- package/dist/runner/native-tools.d.ts +31 -0
- package/dist/runner/native-tools.js +241 -0
- package/dist/runner/process-utils.d.ts +12 -0
- package/dist/runner/process-utils.js +96 -0
- package/dist/runner/run-lifecycle.d.ts +6 -0
- package/dist/runner/run-lifecycle.js +42 -0
- package/dist/runner/types.d.ts +215 -0
- package/dist/runner/types.js +41 -0
- package/dist/scanner/detector.js +42 -663
- package/dist/scanner/detectors/ecosystem-detector.d.ts +16 -0
- package/dist/scanner/detectors/ecosystem-detector.js +354 -0
- package/dist/scanner/detectors/orm-detector.d.ts +5 -0
- package/dist/scanner/detectors/orm-detector.js +59 -0
- package/dist/scanner/detectors/workspace-detector.d.ts +19 -0
- package/dist/scanner/detectors/workspace-detector.js +266 -0
- package/dist/server/cli-runner.d.ts +15 -0
- package/dist/server/cli-runner.js +43 -0
- package/dist/server/event-hub.d.ts +42 -0
- package/dist/server/event-hub.js +169 -0
- package/dist/server/http-utils.d.ts +15 -0
- package/dist/server/http-utils.js +56 -0
- package/dist/server/plan-utils.d.ts +9 -0
- package/dist/server/plan-utils.js +31 -0
- package/dist/server/routes/db-routes.d.ts +57 -0
- package/dist/server/routes/db-routes.js +484 -0
- package/dist/server/routes/task-routes.d.ts +266 -0
- package/dist/server/routes/task-routes.js +285 -0
- package/dist/server/routes/telemetry-routes.d.ts +128 -0
- package/dist/server/routes/telemetry-routes.js +179 -0
- package/dist/server/routes/triage-routes.d.ts +81 -0
- package/dist/server/routes/triage-routes.js +244 -0
- package/dist/server/routes/worktree-routes.d.ts +31 -0
- package/dist/server/routes/worktree-routes.js +144 -0
- package/dist/server/studio-server.d.ts +4 -11
- package/dist/server/studio-server.js +34 -1578
- package/dist/server/studio-ui/shell/pipeline-state.js +2 -0
- package/dist/server/studio-ui/shell/triage.js +10 -0
- package/package.json +2 -2
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/agents/architect.md +4 -2
- package/plugin/agents/verifier.md +1 -1
- package/plugin/agents/worker.md +7 -4
- package/plugin/skills/nativ/SKILL.md +1 -0
- package/templates/.claudeignore +62 -45
- package/templates/AGENTS.md +1 -0
- package/templates/CLAUDE.md +13 -0
- package/templates/claude-agents/architect.md +4 -2
- package/templates/claude-agents/verifier.md +1 -1
- package/templates/claude-agents/worker.md +7 -4
- package/templates/dot-ai/api_contracts.json +66 -66
- package/templates/dot-ai/context.md +38 -38
- package/templates/dot-ai/db_schema.json +39 -39
- package/templates/dot-ai/subagents/architect.md +54 -0
- package/dist/commands/session.d.ts +0 -22
- package/dist/commands/session.js +0 -106
- package/dist/core/session-memory.d.ts +0 -42
- package/dist/core/session-memory.js +0 -121
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Architect Sub-agent Role Specification
|
|
2
|
+
|
|
3
|
+
## Persona & Objective
|
|
4
|
+
|
|
5
|
+
You are the **Architect**. You design; you do not build. Turn a feature request into approved contracts in `.ai/`, then hand back to the project manager. Workers implement. You resolve escalations. You never write application code beyond a trivial fix.
|
|
6
|
+
|
|
7
|
+
## Phase 1: Discovery and design
|
|
8
|
+
|
|
9
|
+
1. Read `.ai/context.md`. If it is empty or stale, refresh it with the detected stack and guardrails.
|
|
10
|
+
2. **Pick the design track** and record it in `.ai/ui_specs.md` under Planning Level.
|
|
11
|
+
- **Experience-First (Outside-In):** UI flows and screen data needs drive the API and the schema. Order: UI, API, schema. Use for user-facing products.
|
|
12
|
+
- **Data-First (Inside-Out):** the core schema or pipeline models drive the API and the UI views. Order: schema, API, UI. Use for data pipelines or an existing schema.
|
|
13
|
+
|
|
14
|
+
STOP. Wait for the human to confirm the track. Design in that order and STOP for explicit approval after each step.
|
|
15
|
+
3. **Database schema.** Propose tables, columns, relations, types and indexes in plain language.
|
|
16
|
+
4. **API contracts.** Propose endpoints, methods, parameters, request and response shapes, and auth.
|
|
17
|
+
5. **UI and UX.** Work in this order:
|
|
18
|
+
- Pick Quick, Standard or Full planning level (the human can override).
|
|
19
|
+
- Ask at most 5 discovery questions, only ones that change the design.
|
|
20
|
+
- Check what can be reused (components, theme, icon set) before inventing.
|
|
21
|
+
- Content first: the top 3 to 5 user tasks with frequency, and real sample data per screen.
|
|
22
|
+
- Standard and Full: fill the Design Brief (who uses it, the job, tone in three words, things to avoid).
|
|
23
|
+
- Full only: two style tiles at `.ai/design/direction-a.html` and `.ai/design/direction-b.html`. Mark one `(Recommended)`, STOP for the human to pick. Keep the pick as `.ai/design/style-tile.html`.
|
|
24
|
+
- Wireframes as ASCII box art or a Semantic Component Tree. STOP for approval.
|
|
25
|
+
- Then component map, copy, Mutation States (loading, optimistic update, rollback) for data-changing components.
|
|
26
|
+
- Task breakdown: one component or endpoint per task, at most 5 files. Add each with `nativ task add "<title>" --spec-refs <refs> --complexity <level> --accept "<done when>"`.
|
|
27
|
+
|
|
28
|
+
## Phase 2: Export
|
|
29
|
+
|
|
30
|
+
Only after all three approvals (schema, API, UI), write:
|
|
31
|
+
|
|
32
|
+
- `.ai/db_schema.json`
|
|
33
|
+
- `.ai/api_contracts.json`
|
|
34
|
+
- `.ai/ui_specs.md`
|
|
35
|
+
- `.ai/master_plan.json` — prefer `nativ task add` over hand-editing. Each task must have `targetFiles`, `acceptanceCriteria`, `specRefs`, `complexity`, and `verificationCommand`.
|
|
36
|
+
|
|
37
|
+
## Phase 3: Hand back
|
|
38
|
+
|
|
39
|
+
Tell the human the contracts are exported and Claude Code can run `nativ task next` to start workers.
|
|
40
|
+
|
|
41
|
+
## Resolving escalations
|
|
42
|
+
|
|
43
|
+
Workers escalate contract gaps with `nativ task escalate`. At the start of a session, or when asked:
|
|
44
|
+
|
|
45
|
+
1. Read items with `"status": "pending_review"` from `.ai/escalation.json`.
|
|
46
|
+
2. Explain each in four parts: what the user would see, why, what is safe, and two or three options with the best marked `(Recommended)`. STOP for a decision.
|
|
47
|
+
3. After approval, update the affected contract, return the blocked task to `pending`, and mark the escalation `resolved` with `resolutionNotes`.
|
|
48
|
+
4. Tell the human the blueprint is re-synchronized.
|
|
49
|
+
|
|
50
|
+
## Constraints
|
|
51
|
+
|
|
52
|
+
- You may write `.ai/` only after the human approves each contract.
|
|
53
|
+
- You may not edit application code, start workers, or approve your own lessons.
|
|
54
|
+
- Use `nativ_db_status`, `nativ_db_inspect`, `nativ_db_diff` for database discovery. Never read row data.
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { SessionCheckpoint, SessionSummaryEntry } from '../core/session-memory.js';
|
|
2
|
-
export interface SessionSaveOptions {
|
|
3
|
-
summary: string;
|
|
4
|
-
decision?: string[];
|
|
5
|
-
obstacle?: string[];
|
|
6
|
-
next?: string[];
|
|
7
|
-
activeFiles?: string;
|
|
8
|
-
task?: string;
|
|
9
|
-
role?: string;
|
|
10
|
-
sessionId?: string;
|
|
11
|
-
json?: boolean;
|
|
12
|
-
}
|
|
13
|
-
export interface SessionListOptions {
|
|
14
|
-
limit?: number;
|
|
15
|
-
json?: boolean;
|
|
16
|
-
}
|
|
17
|
-
export interface SessionGetOptions {
|
|
18
|
-
json?: boolean;
|
|
19
|
-
}
|
|
20
|
-
export declare function runSessionSave(targetDirArg?: string, options?: SessionSaveOptions): Promise<SessionCheckpoint>;
|
|
21
|
-
export declare function runSessionList(targetDirArg?: string, options?: SessionListOptions): Promise<SessionSummaryEntry[]>;
|
|
22
|
-
export declare function runSessionGet(sessionId?: string, targetDirArg?: string, options?: SessionGetOptions): Promise<SessionCheckpoint | null>;
|
package/dist/commands/session.js
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import pc from 'picocolors';
|
|
3
|
-
import { saveSessionCheckpoint, getLatestSession, getSession, listSessions, } from '../core/session-memory.js';
|
|
4
|
-
export async function runSessionSave(targetDirArg, options = { summary: '' }) {
|
|
5
|
-
const targetDir = path.resolve(targetDirArg || process.cwd());
|
|
6
|
-
const files = options.activeFiles
|
|
7
|
-
? options.activeFiles.split(',').map((f) => f.trim()).filter(Boolean)
|
|
8
|
-
: undefined;
|
|
9
|
-
const checkpoint = saveSessionCheckpoint(targetDir, {
|
|
10
|
-
sessionId: options.sessionId,
|
|
11
|
-
summary: options.summary,
|
|
12
|
-
decisions: options.decision,
|
|
13
|
-
obstacles: options.obstacle,
|
|
14
|
-
nextSteps: options.next,
|
|
15
|
-
activeFiles: files,
|
|
16
|
-
taskId: options.task,
|
|
17
|
-
role: options.role,
|
|
18
|
-
});
|
|
19
|
-
if (options.json) {
|
|
20
|
-
console.log(JSON.stringify(checkpoint, null, 2));
|
|
21
|
-
return checkpoint;
|
|
22
|
-
}
|
|
23
|
-
console.log(pc.bold(pc.green(`\n✔ Saved Session Checkpoint: `)) + pc.yellow(`[${checkpoint.sessionId}]`));
|
|
24
|
-
console.log(pc.bold(' Summary: ') + pc.white(checkpoint.summary));
|
|
25
|
-
if (checkpoint.taskId) {
|
|
26
|
-
console.log(pc.bold(' Task: ') + pc.yellow(checkpoint.taskId));
|
|
27
|
-
}
|
|
28
|
-
if (checkpoint.role) {
|
|
29
|
-
console.log(pc.bold(' Role: ') + pc.magenta(checkpoint.role));
|
|
30
|
-
}
|
|
31
|
-
if (checkpoint.decisions && checkpoint.decisions.length > 0) {
|
|
32
|
-
console.log(pc.bold(' Decisions: ') + pc.dim(checkpoint.decisions.join(' | ')));
|
|
33
|
-
}
|
|
34
|
-
if (checkpoint.obstacles && checkpoint.obstacles.length > 0) {
|
|
35
|
-
console.log(pc.bold(' Obstacles: ') + pc.red(checkpoint.obstacles.join(' | ')));
|
|
36
|
-
}
|
|
37
|
-
if (checkpoint.nextSteps && checkpoint.nextSteps.length > 0) {
|
|
38
|
-
console.log(pc.bold(' Next Steps: ') + pc.cyan(checkpoint.nextSteps.join(' | ')));
|
|
39
|
-
}
|
|
40
|
-
if (checkpoint.activeFiles && checkpoint.activeFiles.length > 0) {
|
|
41
|
-
console.log(pc.bold(' Files: ') + pc.dim(checkpoint.activeFiles.join(', ')));
|
|
42
|
-
}
|
|
43
|
-
console.log();
|
|
44
|
-
return checkpoint;
|
|
45
|
-
}
|
|
46
|
-
export async function runSessionList(targetDirArg, options = {}) {
|
|
47
|
-
const targetDir = path.resolve(targetDirArg || process.cwd());
|
|
48
|
-
const sessions = listSessions(targetDir, options.limit || 10);
|
|
49
|
-
if (options.json) {
|
|
50
|
-
console.log(JSON.stringify(sessions, null, 2));
|
|
51
|
-
return sessions;
|
|
52
|
-
}
|
|
53
|
-
console.log(pc.bold(pc.cyan('\nSession Memory Checkpoints (.ai/memory/sessions/)')));
|
|
54
|
-
console.log(pc.dim(`Workspace: ${targetDir}\n`));
|
|
55
|
-
if (sessions.length === 0) {
|
|
56
|
-
console.log(pc.dim(' No saved sessions found.\n'));
|
|
57
|
-
console.log(pc.dim(' Save current progress with: ') + pc.white('nativ session save --summary "..."\n'));
|
|
58
|
-
return sessions;
|
|
59
|
-
}
|
|
60
|
-
for (const s of sessions) {
|
|
61
|
-
const roleTag = s.role ? pc.magenta(`[${s.role}] `) : '';
|
|
62
|
-
const taskTag = s.taskId ? pc.yellow(`[${s.taskId}] `) : '';
|
|
63
|
-
console.log(` ${pc.bold(pc.yellow(s.sessionId))} ${pc.dim(s.timestamp)} ${roleTag}${taskTag}\n ${pc.white(s.summary)}`);
|
|
64
|
-
}
|
|
65
|
-
console.log();
|
|
66
|
-
return sessions;
|
|
67
|
-
}
|
|
68
|
-
export async function runSessionGet(sessionId, targetDirArg, options = {}) {
|
|
69
|
-
const targetDir = path.resolve(targetDirArg || process.cwd());
|
|
70
|
-
const session = sessionId ? getSession(targetDir, sessionId) : getLatestSession(targetDir);
|
|
71
|
-
if (options.json) {
|
|
72
|
-
console.log(JSON.stringify(session, null, 2));
|
|
73
|
-
return session;
|
|
74
|
-
}
|
|
75
|
-
if (!session) {
|
|
76
|
-
console.log(pc.yellow(`\nNo session found${sessionId ? ` matching [${sessionId}]` : ''}.\n`));
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
console.log(pc.bold(pc.cyan(`\nSession Details: `)) + pc.yellow(`[${session.sessionId}]`));
|
|
80
|
-
console.log(pc.bold(' Timestamp: ') + pc.dim(session.timestamp));
|
|
81
|
-
if (session.taskId)
|
|
82
|
-
console.log(pc.bold(' Task ID: ') + pc.yellow(session.taskId));
|
|
83
|
-
if (session.role)
|
|
84
|
-
console.log(pc.bold(' Role: ') + pc.magenta(session.role));
|
|
85
|
-
console.log(pc.bold(' Summary: ') + pc.white(session.summary));
|
|
86
|
-
if (session.decisions && session.decisions.length > 0) {
|
|
87
|
-
console.log(pc.bold('\n Key Decisions:'));
|
|
88
|
-
for (const d of session.decisions)
|
|
89
|
-
console.log(pc.dim(' • ') + pc.white(d));
|
|
90
|
-
}
|
|
91
|
-
if (session.obstacles && session.obstacles.length > 0) {
|
|
92
|
-
console.log(pc.bold('\n Obstacles / Blockers:'));
|
|
93
|
-
for (const o of session.obstacles)
|
|
94
|
-
console.log(pc.red(' ✖ ') + pc.white(o));
|
|
95
|
-
}
|
|
96
|
-
if (session.nextSteps && session.nextSteps.length > 0) {
|
|
97
|
-
console.log(pc.bold('\n Next Steps:'));
|
|
98
|
-
for (const n of session.nextSteps)
|
|
99
|
-
console.log(pc.cyan(' → ') + pc.white(n));
|
|
100
|
-
}
|
|
101
|
-
if (session.activeFiles && session.activeFiles.length > 0) {
|
|
102
|
-
console.log(pc.bold('\n Active Files: ') + pc.dim(session.activeFiles.join(', ')));
|
|
103
|
-
}
|
|
104
|
-
console.log();
|
|
105
|
-
return session;
|
|
106
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
export interface SessionCheckpoint {
|
|
2
|
-
sessionId: string;
|
|
3
|
-
timestamp: string;
|
|
4
|
-
role?: string;
|
|
5
|
-
taskId?: string;
|
|
6
|
-
summary: string;
|
|
7
|
-
decisions?: string[];
|
|
8
|
-
obstacles?: string[];
|
|
9
|
-
nextSteps?: string[];
|
|
10
|
-
activeFiles?: string[];
|
|
11
|
-
}
|
|
12
|
-
export interface SessionSummaryEntry {
|
|
13
|
-
sessionId: string;
|
|
14
|
-
timestamp: string;
|
|
15
|
-
role?: string;
|
|
16
|
-
taskId?: string;
|
|
17
|
-
summary: string;
|
|
18
|
-
}
|
|
19
|
-
export interface SessionIndex {
|
|
20
|
-
version: '1.0.0';
|
|
21
|
-
updatedAt: string;
|
|
22
|
-
latestSessionId?: string;
|
|
23
|
-
sessions: SessionSummaryEntry[];
|
|
24
|
-
}
|
|
25
|
-
export declare function getSessionMemoryDir(targetDir: string): string;
|
|
26
|
-
export declare function getSessionIndexPath(targetDir: string): string;
|
|
27
|
-
export declare function getLatestSessionPath(targetDir: string): string;
|
|
28
|
-
export declare function loadSessionIndex(targetDir: string): SessionIndex;
|
|
29
|
-
export declare function saveSessionIndex(targetDir: string, index: SessionIndex): void;
|
|
30
|
-
export declare function saveSessionCheckpoint(targetDir: string, input: {
|
|
31
|
-
sessionId?: string;
|
|
32
|
-
role?: string;
|
|
33
|
-
taskId?: string;
|
|
34
|
-
summary: string;
|
|
35
|
-
decisions?: string[];
|
|
36
|
-
obstacles?: string[];
|
|
37
|
-
nextSteps?: string[];
|
|
38
|
-
activeFiles?: string[];
|
|
39
|
-
}): SessionCheckpoint;
|
|
40
|
-
export declare function getLatestSession(targetDir: string): SessionCheckpoint | null;
|
|
41
|
-
export declare function getSession(targetDir: string, sessionId: string): SessionCheckpoint | null;
|
|
42
|
-
export declare function listSessions(targetDir: string, limit?: number): SessionSummaryEntry[];
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
export function getSessionMemoryDir(targetDir) {
|
|
4
|
-
return path.join(targetDir, '.ai', 'memory', 'sessions');
|
|
5
|
-
}
|
|
6
|
-
export function getSessionIndexPath(targetDir) {
|
|
7
|
-
return path.join(getSessionMemoryDir(targetDir), 'index.json');
|
|
8
|
-
}
|
|
9
|
-
export function getLatestSessionPath(targetDir) {
|
|
10
|
-
return path.join(getSessionMemoryDir(targetDir), 'latest.json');
|
|
11
|
-
}
|
|
12
|
-
export function loadSessionIndex(targetDir) {
|
|
13
|
-
const indexPath = getSessionIndexPath(targetDir);
|
|
14
|
-
if (!fs.existsSync(indexPath)) {
|
|
15
|
-
return {
|
|
16
|
-
version: '1.0.0',
|
|
17
|
-
updatedAt: new Date().toISOString(),
|
|
18
|
-
sessions: [],
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
try {
|
|
22
|
-
const raw = fs.readFileSync(indexPath, 'utf8');
|
|
23
|
-
const parsed = JSON.parse(raw);
|
|
24
|
-
if (!Array.isArray(parsed.sessions)) {
|
|
25
|
-
return {
|
|
26
|
-
version: '1.0.0',
|
|
27
|
-
updatedAt: new Date().toISOString(),
|
|
28
|
-
sessions: [],
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
return parsed;
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
return {
|
|
35
|
-
version: '1.0.0',
|
|
36
|
-
updatedAt: new Date().toISOString(),
|
|
37
|
-
sessions: [],
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
export function saveSessionIndex(targetDir, index) {
|
|
42
|
-
const dir = getSessionMemoryDir(targetDir);
|
|
43
|
-
if (!fs.existsSync(dir)) {
|
|
44
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
45
|
-
}
|
|
46
|
-
index.updatedAt = new Date().toISOString();
|
|
47
|
-
fs.writeFileSync(getSessionIndexPath(targetDir), JSON.stringify(index, null, 2), 'utf8');
|
|
48
|
-
}
|
|
49
|
-
export function saveSessionCheckpoint(targetDir, input) {
|
|
50
|
-
const dir = getSessionMemoryDir(targetDir);
|
|
51
|
-
if (!fs.existsSync(dir)) {
|
|
52
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
53
|
-
}
|
|
54
|
-
const now = new Date();
|
|
55
|
-
const dateStr = now.toISOString().replace(/[-:T]/g, '').slice(0, 14);
|
|
56
|
-
const sessionId = input.sessionId || `sess-${dateStr}`;
|
|
57
|
-
const checkpoint = {
|
|
58
|
-
sessionId,
|
|
59
|
-
timestamp: now.toISOString(),
|
|
60
|
-
...(input.role ? { role: input.role.trim() } : {}),
|
|
61
|
-
...(input.taskId ? { taskId: input.taskId.trim() } : {}),
|
|
62
|
-
summary: input.summary.trim(),
|
|
63
|
-
...(input.decisions && input.decisions.length > 0 ? { decisions: input.decisions.map((d) => d.trim()) } : {}),
|
|
64
|
-
...(input.obstacles && input.obstacles.length > 0 ? { obstacles: input.obstacles.map((o) => o.trim()) } : {}),
|
|
65
|
-
...(input.nextSteps && input.nextSteps.length > 0 ? { nextSteps: input.nextSteps.map((n) => n.trim()) } : {}),
|
|
66
|
-
...(input.activeFiles && input.activeFiles.length > 0 ? { activeFiles: input.activeFiles.map((f) => f.trim()) } : {}),
|
|
67
|
-
};
|
|
68
|
-
// Write individual session file
|
|
69
|
-
const sessionPath = path.join(dir, `${sessionId}.json`);
|
|
70
|
-
fs.writeFileSync(sessionPath, JSON.stringify(checkpoint, null, 2), 'utf8');
|
|
71
|
-
// Write latest.json
|
|
72
|
-
fs.writeFileSync(getLatestSessionPath(targetDir), JSON.stringify(checkpoint, null, 2), 'utf8');
|
|
73
|
-
// Update index
|
|
74
|
-
const index = loadSessionIndex(targetDir);
|
|
75
|
-
index.latestSessionId = sessionId;
|
|
76
|
-
// Remove existing entry with same sessionId if updating
|
|
77
|
-
index.sessions = index.sessions.filter((s) => s.sessionId !== sessionId);
|
|
78
|
-
index.sessions.unshift({
|
|
79
|
-
sessionId,
|
|
80
|
-
timestamp: checkpoint.timestamp,
|
|
81
|
-
role: checkpoint.role,
|
|
82
|
-
taskId: checkpoint.taskId,
|
|
83
|
-
summary: checkpoint.summary,
|
|
84
|
-
});
|
|
85
|
-
// Keep index trimmed to 50 most recent sessions
|
|
86
|
-
if (index.sessions.length > 50) {
|
|
87
|
-
index.sessions = index.sessions.slice(0, 50);
|
|
88
|
-
}
|
|
89
|
-
saveSessionIndex(targetDir, index);
|
|
90
|
-
return checkpoint;
|
|
91
|
-
}
|
|
92
|
-
export function getLatestSession(targetDir) {
|
|
93
|
-
const latestPath = getLatestSessionPath(targetDir);
|
|
94
|
-
if (!fs.existsSync(latestPath)) {
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
try {
|
|
98
|
-
const raw = fs.readFileSync(latestPath, 'utf8');
|
|
99
|
-
return JSON.parse(raw);
|
|
100
|
-
}
|
|
101
|
-
catch {
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
export function getSession(targetDir, sessionId) {
|
|
106
|
-
const sessionPath = path.join(getSessionMemoryDir(targetDir), `${sessionId}.json`);
|
|
107
|
-
if (!fs.existsSync(sessionPath)) {
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
try {
|
|
111
|
-
const raw = fs.readFileSync(sessionPath, 'utf8');
|
|
112
|
-
return JSON.parse(raw);
|
|
113
|
-
}
|
|
114
|
-
catch {
|
|
115
|
-
return null;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
export function listSessions(targetDir, limit = 10) {
|
|
119
|
-
const index = loadSessionIndex(targetDir);
|
|
120
|
-
return index.sessions.slice(0, limit);
|
|
121
|
-
}
|