openhermes 4.12.1 → 4.13.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/CONTEXT.md +6 -6
- package/ETHOS.md +2 -2
- package/README.md +11 -17
- package/bootstrap.ts +118 -126
- package/docs/HOW-IT-WORKS.md +162 -0
- package/docs/adr/ADR-0001-rebuild-vs-increment.md +30 -0
- package/docs/adr/ADR-0002-routing-graph-vs-linear-chain.md +36 -0
- package/docs/adr/ADR-0003-per-directory-plan-storage.md +34 -0
- package/docs/adr/ADR-0004-composer-fragment-architecture.md +42 -0
- package/docs/adr/ADR-0005-hook-system-design.md +42 -0
- package/docs/adr/README.md +9 -0
- package/harness/codex/AUTOPILOT.md +35 -40
- package/harness/codex/CHARTER.md +3 -3
- package/harness/lib/composer/compose.test.ts +29 -29
- package/harness/lib/composer/fragments/02-delegation.md +5 -5
- package/harness/lib/composer/fragments/04-task-flow.md +13 -13
- package/harness/lib/composer/fragments/08-routing.md +1 -1
- package/harness/lib/composer/fragments/09-guardrails.md +25 -25
- package/harness/lib/composer/index.ts +1 -1
- package/harness/lib/guards/guard-config.ts +72 -72
- package/harness/lib/hooks/builtins/confidence-gate-hook.ts +9 -9
- package/harness/lib/hooks/builtins/delegation-depth-hook.ts +1 -1
- package/harness/lib/hooks/builtins/dynamic-route-hook.ts +99 -99
- package/harness/lib/hooks/builtins/next-route-hook.ts +24 -24
- package/harness/lib/hooks/builtins/plan-check-hook.ts +5 -5
- package/harness/lib/hooks/builtins/route-tracking-hook.ts +1 -1
- package/harness/lib/hooks/hooks.test.ts +160 -324
- package/harness/lib/hooks/index.ts +38 -42
- package/harness/lib/hooks/registry.ts +309 -416
- package/harness/lib/hooks/types.ts +116 -119
- package/harness/lib/plans/plan-location.ts +134 -134
- package/harness/lib/routing/index.ts +21 -21
- package/harness/lib/routing/route-guidance.ts +147 -147
- package/harness/lib/routing/route-resolver.ts +58 -58
- package/harness/lib/routing/routing.test.ts +195 -195
- package/harness/lib/routing/skill-frontmatter.ts +125 -125
- package/harness/lib/routing/types.ts +52 -52
- package/harness/skills/oh-ascii/SKILL.md +1 -1
- package/harness/skills/oh-fusion/DEEP.md +109 -109
- package/harness/skills/oh-fusion/SKILL.md +47 -47
- package/harness/skills/oh-init/DEEP.md +2 -2
- package/harness/skills/oh-plan-review/DEEP.md +1 -1
- package/harness/skills/oh-planner/DEEP.md +3 -3
- package/harness/skills/oh-review/DEEP.md +5 -5
- package/package.json +56 -53
- package/harness/lib/background/background.test.ts +0 -216
- package/harness/lib/background/index.ts +0 -7
- package/harness/lib/background/interfaces.ts +0 -31
- package/harness/lib/background/manager.ts +0 -320
- package/harness/lib/hooks/builtins/error-recovery-hook.ts +0 -107
- package/harness/lib/hooks/builtins/memory-sync-hook.ts +0 -73
- package/harness/lib/hooks/builtins/sanity-check-hook.ts +0 -52
- package/harness/lib/hooks/builtins/subagent-failure-hook.ts +0 -93
- package/harness/lib/memory/index.ts +0 -18
- package/harness/lib/memory/interfaces.ts +0 -53
- package/harness/lib/memory/memory-manager.ts +0 -205
- package/harness/lib/memory/memory.test.ts +0 -485
- package/harness/lib/memory/plan-store.ts +0 -346
- package/harness/lib/recovery/handler.ts +0 -243
- package/harness/lib/recovery/index.ts +0 -14
- package/harness/lib/recovery/interfaces.ts +0 -48
- package/harness/lib/recovery/patterns.ts +0 -149
- package/harness/lib/recovery/recovery.test.ts +0 -312
- package/harness/lib/sanity/anomaly-tracker.ts +0 -127
- package/harness/lib/sanity/checker.ts +0 -189
- package/harness/lib/sanity/index.ts +0 -13
- package/harness/lib/sanity/interfaces.ts +0 -24
- package/harness/lib/sanity/sanity.test.ts +0 -472
- package/harness/lib/sync/file-watcher.ts +0 -175
- package/harness/lib/sync/index.ts +0 -11
- package/harness/lib/sync/interfaces.ts +0 -27
- package/harness/lib/sync/plan-sync.ts +0 -533
- package/harness/lib/sync/sync.test.ts +0 -858
|
@@ -1,119 +1,116 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// Hook System — type definitions
|
|
3
|
-
// ---------------------------------------------------------------------------
|
|
4
|
-
|
|
5
|
-
export enum HookPhase {
|
|
6
|
-
EARLY = 0,
|
|
7
|
-
NORMAL = 1,
|
|
8
|
-
LATE = 2,
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface HookContextBase {
|
|
12
|
-
sessionId: string;
|
|
13
|
-
agent: string;
|
|
14
|
-
directory: string;
|
|
15
|
-
sessions: Map<string, unknown>;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface HookContextExtras {
|
|
19
|
-
_planCheck?: "missing" | "found";
|
|
20
|
-
_planFilePath?: string;
|
|
21
|
-
_planCheckInstruction?: string;
|
|
22
|
-
|
|
23
|
-
_shellPlatform?: string;
|
|
24
|
-
_shellType?: string;
|
|
25
|
-
_shellPreamble?: string;
|
|
26
|
-
|
|
27
|
-
_delegationDepth?: number;
|
|
28
|
-
_depthExceeded?: boolean;
|
|
29
|
-
_depthError?: string;
|
|
30
|
-
|
|
31
|
-
_confidenceLevel?: "HIGH" | "MEDIUM" | "LOW" | string;
|
|
32
|
-
_confidenceExchanges?: number;
|
|
33
|
-
|
|
34
|
-
// Guard configuration (centralized — replaces _routeTrackingConfig and _maxDelegationDepth)
|
|
35
|
-
_guardConfig?: import("../guards/guard-config.ts").GuardConfig;
|
|
36
|
-
_guardProgression?: import("../guards/guard-config.ts").GuardProgression;
|
|
37
|
-
|
|
38
|
-
// Subagent failure tracking
|
|
39
|
-
_subagentFailures?: number;
|
|
40
|
-
_subagentFailureThreshold?: number;
|
|
41
|
-
_optiRoute?: {
|
|
42
|
-
reason: string;
|
|
43
|
-
chain: Array<{
|
|
44
|
-
skill: string;
|
|
45
|
-
timestamp: number;
|
|
46
|
-
producedArtifact: boolean;
|
|
47
|
-
}>;
|
|
48
|
-
skillCounts: Record<string, number>;
|
|
49
|
-
unproductiveCount: number;
|
|
50
|
-
maxSkillRepeats: number;
|
|
51
|
-
maxUnproductiveHops: number;
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
export type
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
|
117
|
-
| PostToolUseHook
|
|
118
|
-
| RouteHook
|
|
119
|
-
| SessionHook;
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Hook System — type definitions
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
export enum HookPhase {
|
|
6
|
+
EARLY = 0,
|
|
7
|
+
NORMAL = 1,
|
|
8
|
+
LATE = 2,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface HookContextBase {
|
|
12
|
+
sessionId: string;
|
|
13
|
+
agent: string;
|
|
14
|
+
directory: string;
|
|
15
|
+
sessions: Map<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface HookContextExtras {
|
|
19
|
+
_planCheck?: "missing" | "found";
|
|
20
|
+
_planFilePath?: string;
|
|
21
|
+
_planCheckInstruction?: string;
|
|
22
|
+
|
|
23
|
+
_shellPlatform?: string;
|
|
24
|
+
_shellType?: string;
|
|
25
|
+
_shellPreamble?: string;
|
|
26
|
+
|
|
27
|
+
_delegationDepth?: number;
|
|
28
|
+
_depthExceeded?: boolean;
|
|
29
|
+
_depthError?: string;
|
|
30
|
+
|
|
31
|
+
_confidenceLevel?: "HIGH" | "MEDIUM" | "LOW" | string;
|
|
32
|
+
_confidenceExchanges?: number;
|
|
33
|
+
|
|
34
|
+
// Guard configuration (centralized — replaces _routeTrackingConfig and _maxDelegationDepth)
|
|
35
|
+
_guardConfig?: import("../guards/guard-config.ts").GuardConfig;
|
|
36
|
+
_guardProgression?: import("../guards/guard-config.ts").GuardProgression;
|
|
37
|
+
|
|
38
|
+
// Subagent failure tracking
|
|
39
|
+
_subagentFailures?: number;
|
|
40
|
+
_subagentFailureThreshold?: number;
|
|
41
|
+
_optiRoute?: {
|
|
42
|
+
reason: string;
|
|
43
|
+
chain: Array<{
|
|
44
|
+
skill: string;
|
|
45
|
+
timestamp: number;
|
|
46
|
+
producedArtifact: boolean;
|
|
47
|
+
}>;
|
|
48
|
+
skillCounts: Record<string, number>;
|
|
49
|
+
unproductiveCount: number;
|
|
50
|
+
maxSkillRepeats: number;
|
|
51
|
+
maxUnproductiveHops: number;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
_routingSkillsDir?: string;
|
|
55
|
+
_nextRoute?: import("../routing/index.ts").RuntimeRouteDecision;
|
|
56
|
+
|
|
57
|
+
[key: string]: unknown;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type HookContext = HookContextBase & HookContextExtras;
|
|
61
|
+
|
|
62
|
+
export type HookContextPatch = Partial<HookContextBase> &
|
|
63
|
+
Partial<HookContextExtras>;
|
|
64
|
+
|
|
65
|
+
export interface HookMetadata {
|
|
66
|
+
name: string;
|
|
67
|
+
priority: number; // 0-100, higher = earlier within phase
|
|
68
|
+
phase: HookPhase;
|
|
69
|
+
dependencies: string[]; // hook names this depends on
|
|
70
|
+
errorHandling: "propagate" | "isolate" | "retry";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export enum HookResult {
|
|
74
|
+
CONTINUE = "continue",
|
|
75
|
+
STOP = "stop",
|
|
76
|
+
INJECT = "inject",
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface PreToolUseHook {
|
|
80
|
+
metadata: HookMetadata;
|
|
81
|
+
execute(
|
|
82
|
+
context: HookContext,
|
|
83
|
+
): Promise<{ result: HookResult; modifiedContext?: HookContextPatch }>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface PostToolUseHook {
|
|
87
|
+
metadata: HookMetadata;
|
|
88
|
+
execute(
|
|
89
|
+
context: HookContext,
|
|
90
|
+
output: string,
|
|
91
|
+
): Promise<{
|
|
92
|
+
result: HookResult;
|
|
93
|
+
modifiedOutput?: string;
|
|
94
|
+
}>;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface RouteHook {
|
|
98
|
+
metadata: HookMetadata;
|
|
99
|
+
execute(
|
|
100
|
+
context: HookContext,
|
|
101
|
+
route: string,
|
|
102
|
+
): Promise<{ result: HookResult; modifiedRoute?: string }>;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface SessionHook {
|
|
106
|
+
metadata: HookMetadata;
|
|
107
|
+
onSessionStart(context: HookContext): Promise<void>;
|
|
108
|
+
onSessionEnd(context: HookContext): Promise<void>;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Union type for any hook in the registry
|
|
112
|
+
export type AnyHook =
|
|
113
|
+
| PreToolUseHook
|
|
114
|
+
| PostToolUseHook
|
|
115
|
+
| RouteHook
|
|
116
|
+
| SessionHook;
|
|
@@ -1,134 +1,134 @@
|
|
|
1
|
-
import fs from "node:fs"
|
|
2
|
-
import os from "node:os"
|
|
3
|
-
import path from "node:path"
|
|
4
|
-
|
|
5
|
-
let _planStorageOverride: string | undefined
|
|
6
|
-
|
|
7
|
-
export interface PlanAccess {
|
|
8
|
-
path: string
|
|
9
|
-
status: string | null
|
|
10
|
-
objective: string | null
|
|
11
|
-
summary: string | null
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function setPlanStorageDirForTest(dir: string | undefined): void {
|
|
15
|
-
_planStorageOverride = dir
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function planStorageDir(): string {
|
|
19
|
-
return _planStorageOverride ?? path.join(os.homedir(), ".local", "share", "openhermes", "plans")
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function getProjectName(projectDir: string): string {
|
|
23
|
-
return path.basename(projectDir)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function ensureDir(dir: string): void {
|
|
27
|
-
try {
|
|
28
|
-
if (!fs.existsSync(dir)) {
|
|
29
|
-
fs.mkdirSync(dir, { recursive: true })
|
|
30
|
-
}
|
|
31
|
-
} catch (err) {
|
|
32
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
33
|
-
console.error(`[openhermes] Failed to create directory ${dir}: ${msg}`)
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function readPlanAccess(filePath: string): PlanAccess | null {
|
|
38
|
-
if (!fs.existsSync(filePath)) return null
|
|
39
|
-
const source = fs.readFileSync(filePath, "utf8")
|
|
40
|
-
const status = source.match(/^Status:\s*(.+)$/m)?.[1]?.trim() ?? null
|
|
41
|
-
const objective = source.match(/^Objective:\s*(.+)$/m)?.[1]?.trim() ?? null
|
|
42
|
-
if (!status && !objective) return null
|
|
43
|
-
const parts = [status ? `status=${status}` : null, objective ? `objective=${objective}` : null].filter(Boolean)
|
|
44
|
-
return {
|
|
45
|
-
path: filePath,
|
|
46
|
-
status,
|
|
47
|
-
objective,
|
|
48
|
-
summary: `Active plan: ${parts.join(" | ")}`,
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function resolvePlanAccess(projectDir: string): PlanAccess | null {
|
|
53
|
-
const latest = findLatestPlanFile(projectDir)
|
|
54
|
-
if (!latest) return null
|
|
55
|
-
return readPlanAccess(latest)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function findLatestPlanFile(projectDir: string): string | null {
|
|
59
|
-
const projectName = getProjectName(projectDir)
|
|
60
|
-
const storage = planStorageDir()
|
|
61
|
-
const projectDirPath = path.join(storage, projectName)
|
|
62
|
-
if (!fs.existsSync(projectDirPath)) return null
|
|
63
|
-
let latest: string | null = null
|
|
64
|
-
let highest = -1
|
|
65
|
-
try {
|
|
66
|
-
for (const entry of fs.readdirSync(projectDirPath)) {
|
|
67
|
-
const m = entry.match(/^plan-(\d{3})\.md$/)
|
|
68
|
-
if (m) {
|
|
69
|
-
const n = parseInt(m[1], 10)
|
|
70
|
-
if (n > highest) {
|
|
71
|
-
highest = n
|
|
72
|
-
latest = path.join(projectDirPath, entry)
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
} catch {
|
|
77
|
-
return null
|
|
78
|
-
}
|
|
79
|
-
return latest
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export function ensurePlanFile(projectDir: string): string {
|
|
83
|
-
const access = resolvePlanAccess(projectDir)
|
|
84
|
-
if (access?.status === "active" || access?.status === "in-progress") {
|
|
85
|
-
return access.path
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const projectName = getProjectName(projectDir)
|
|
89
|
-
const storage = planStorageDir()
|
|
90
|
-
const projectDirPath = path.join(storage, projectName)
|
|
91
|
-
ensureDir(projectDirPath)
|
|
92
|
-
|
|
93
|
-
const latest = access?.path ?? findLatestPlanFile(projectDir)
|
|
94
|
-
let nextSeq = 1
|
|
95
|
-
if (latest) {
|
|
96
|
-
const m = path.basename(latest).match(/^plan-(\d{3})\.md$/)
|
|
97
|
-
if (m) nextSeq = parseInt(m[1], 10) + 1
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const seq = String(nextSeq).padStart(3, "0")
|
|
101
|
-
const planId = `${projectName}/plan-${seq}.md`
|
|
102
|
-
const planPath = path.join(projectDirPath, `plan-${seq}.md`)
|
|
103
|
-
const now = new Date().toISOString().replace("T", " ").slice(0, 16)
|
|
104
|
-
|
|
105
|
-
const content = [
|
|
106
|
-
`# PLAN: ${projectName}`,
|
|
107
|
-
"",
|
|
108
|
-
`Plan ID: ${planId}`,
|
|
109
|
-
`Project: ${projectName}`,
|
|
110
|
-
`Status: active`,
|
|
111
|
-
`Created: ${now}`,
|
|
112
|
-
`Updated: ${now}`,
|
|
113
|
-
`Project Path: ${projectDir}`,
|
|
114
|
-
`Plan Path: ${planPath}`,
|
|
115
|
-
`Objective: (pending classification)`,
|
|
116
|
-
"",
|
|
117
|
-
"## Tasks",
|
|
118
|
-
"",
|
|
119
|
-
"- [ ] (discoverable — pending classification)",
|
|
120
|
-
"",
|
|
121
|
-
].join("\n")
|
|
122
|
-
|
|
123
|
-
try {
|
|
124
|
-
fs.writeFileSync(planPath, content, "utf8")
|
|
125
|
-
} catch (err) {
|
|
126
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
127
|
-
console.error(`[openhermes] Failed to write plan file ${planPath}: ${msg}`)
|
|
128
|
-
}
|
|
129
|
-
return planPath
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export function readPlanSummary(projectDir: string): string | null {
|
|
133
|
-
return resolvePlanAccess(projectDir)?.summary ?? null
|
|
134
|
-
}
|
|
1
|
+
import fs from "node:fs"
|
|
2
|
+
import os from "node:os"
|
|
3
|
+
import path from "node:path"
|
|
4
|
+
|
|
5
|
+
let _planStorageOverride: string | undefined
|
|
6
|
+
|
|
7
|
+
export interface PlanAccess {
|
|
8
|
+
path: string
|
|
9
|
+
status: string | null
|
|
10
|
+
objective: string | null
|
|
11
|
+
summary: string | null
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function setPlanStorageDirForTest(dir: string | undefined): void {
|
|
15
|
+
_planStorageOverride = dir
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function planStorageDir(): string {
|
|
19
|
+
return _planStorageOverride ?? path.join(os.homedir(), ".local", "share", "openhermes", "plans")
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getProjectName(projectDir: string): string {
|
|
23
|
+
return path.basename(projectDir)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function ensureDir(dir: string): void {
|
|
27
|
+
try {
|
|
28
|
+
if (!fs.existsSync(dir)) {
|
|
29
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
30
|
+
}
|
|
31
|
+
} catch (err) {
|
|
32
|
+
const msg = err instanceof Error ? err.message : String(err)
|
|
33
|
+
console.error(`[openhermes] Failed to create directory ${dir}: ${msg}`)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function readPlanAccess(filePath: string): PlanAccess | null {
|
|
38
|
+
if (!fs.existsSync(filePath)) return null
|
|
39
|
+
const source = fs.readFileSync(filePath, "utf8")
|
|
40
|
+
const status = source.match(/^Status:\s*(.+)$/m)?.[1]?.trim() ?? null
|
|
41
|
+
const objective = source.match(/^Objective:\s*(.+)$/m)?.[1]?.trim() ?? null
|
|
42
|
+
if (!status && !objective) return null
|
|
43
|
+
const parts = [status ? `status=${status}` : null, objective ? `objective=${objective}` : null].filter(Boolean)
|
|
44
|
+
return {
|
|
45
|
+
path: filePath,
|
|
46
|
+
status,
|
|
47
|
+
objective,
|
|
48
|
+
summary: `Active plan: ${parts.join(" | ")}`,
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function resolvePlanAccess(projectDir: string): PlanAccess | null {
|
|
53
|
+
const latest = findLatestPlanFile(projectDir)
|
|
54
|
+
if (!latest) return null
|
|
55
|
+
return readPlanAccess(latest)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function findLatestPlanFile(projectDir: string): string | null {
|
|
59
|
+
const projectName = getProjectName(projectDir)
|
|
60
|
+
const storage = planStorageDir()
|
|
61
|
+
const projectDirPath = path.join(storage, projectName)
|
|
62
|
+
if (!fs.existsSync(projectDirPath)) return null
|
|
63
|
+
let latest: string | null = null
|
|
64
|
+
let highest = -1
|
|
65
|
+
try {
|
|
66
|
+
for (const entry of fs.readdirSync(projectDirPath)) {
|
|
67
|
+
const m = entry.match(/^plan-(\d{3})\.md$/)
|
|
68
|
+
if (m) {
|
|
69
|
+
const n = parseInt(m[1], 10)
|
|
70
|
+
if (n > highest) {
|
|
71
|
+
highest = n
|
|
72
|
+
latest = path.join(projectDirPath, entry)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} catch {
|
|
77
|
+
return null
|
|
78
|
+
}
|
|
79
|
+
return latest
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function ensurePlanFile(projectDir: string): string {
|
|
83
|
+
const access = resolvePlanAccess(projectDir)
|
|
84
|
+
if (access?.status === "active" || access?.status === "in-progress") {
|
|
85
|
+
return access.path
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const projectName = getProjectName(projectDir)
|
|
89
|
+
const storage = planStorageDir()
|
|
90
|
+
const projectDirPath = path.join(storage, projectName)
|
|
91
|
+
ensureDir(projectDirPath)
|
|
92
|
+
|
|
93
|
+
const latest = access?.path ?? findLatestPlanFile(projectDir)
|
|
94
|
+
let nextSeq = 1
|
|
95
|
+
if (latest) {
|
|
96
|
+
const m = path.basename(latest).match(/^plan-(\d{3})\.md$/)
|
|
97
|
+
if (m) nextSeq = parseInt(m[1], 10) + 1
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const seq = String(nextSeq).padStart(3, "0")
|
|
101
|
+
const planId = `${projectName}/plan-${seq}.md`
|
|
102
|
+
const planPath = path.join(projectDirPath, `plan-${seq}.md`)
|
|
103
|
+
const now = new Date().toISOString().replace("T", " ").slice(0, 16)
|
|
104
|
+
|
|
105
|
+
const content = [
|
|
106
|
+
`# PLAN: ${projectName}`,
|
|
107
|
+
"",
|
|
108
|
+
`Plan ID: ${planId}`,
|
|
109
|
+
`Project: ${projectName}`,
|
|
110
|
+
`Status: active`,
|
|
111
|
+
`Created: ${now}`,
|
|
112
|
+
`Updated: ${now}`,
|
|
113
|
+
`Project Path: ${projectDir}`,
|
|
114
|
+
`Plan Path: ${planPath}`,
|
|
115
|
+
`Objective: (pending classification)`,
|
|
116
|
+
"",
|
|
117
|
+
"## Tasks",
|
|
118
|
+
"",
|
|
119
|
+
"- [ ] (discoverable — pending classification)",
|
|
120
|
+
"",
|
|
121
|
+
].join("\n")
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
fs.writeFileSync(planPath, content, "utf8")
|
|
125
|
+
} catch (err) {
|
|
126
|
+
const msg = err instanceof Error ? err.message : String(err)
|
|
127
|
+
console.error(`[openhermes] Failed to write plan file ${planPath}: ${msg}`)
|
|
128
|
+
}
|
|
129
|
+
return planPath
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function readPlanSummary(projectDir: string): string | null {
|
|
133
|
+
return resolvePlanAccess(projectDir)?.summary ?? null
|
|
134
|
+
}
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
export { extractFrontmatter, parseSkillFrontmatter, readSkillFrontmatter, emptySkillRoutes } from "./skill-frontmatter.ts";
|
|
2
|
-
export { resolveRoute } from "./route-resolver.ts";
|
|
3
|
-
export {
|
|
4
|
-
clearRuntimeRouteDecision,
|
|
5
|
-
consumeRouteGuidance,
|
|
6
|
-
extractRouteGuidance,
|
|
7
|
-
extractRuntimeRouteDecision,
|
|
8
|
-
getRuntimeRouteDecision,
|
|
9
|
-
NEXT_ROUTE_PREFIX,
|
|
10
|
-
rememberRuntimeRouteDecision,
|
|
11
|
-
ROUTE_GUIDANCE_PREFIX,
|
|
12
|
-
} from "./route-guidance.ts";
|
|
13
|
-
export { ROUTE_OUTCOMES } from "./types.ts";
|
|
14
|
-
export type {
|
|
15
|
-
RouteEvidence,
|
|
16
|
-
RouteOutcome,
|
|
17
|
-
RouteResolution,
|
|
18
|
-
RuntimeRouteDecision,
|
|
19
|
-
SkillRouteMap,
|
|
20
|
-
SkillRoutingFrontmatter,
|
|
21
|
-
} from "./types.ts";
|
|
1
|
+
export { extractFrontmatter, parseSkillFrontmatter, readSkillFrontmatter, emptySkillRoutes } from "./skill-frontmatter.ts";
|
|
2
|
+
export { resolveRoute } from "./route-resolver.ts";
|
|
3
|
+
export {
|
|
4
|
+
clearRuntimeRouteDecision,
|
|
5
|
+
consumeRouteGuidance,
|
|
6
|
+
extractRouteGuidance,
|
|
7
|
+
extractRuntimeRouteDecision,
|
|
8
|
+
getRuntimeRouteDecision,
|
|
9
|
+
NEXT_ROUTE_PREFIX,
|
|
10
|
+
rememberRuntimeRouteDecision,
|
|
11
|
+
ROUTE_GUIDANCE_PREFIX,
|
|
12
|
+
} from "./route-guidance.ts";
|
|
13
|
+
export { ROUTE_OUTCOMES } from "./types.ts";
|
|
14
|
+
export type {
|
|
15
|
+
RouteEvidence,
|
|
16
|
+
RouteOutcome,
|
|
17
|
+
RouteResolution,
|
|
18
|
+
RuntimeRouteDecision,
|
|
19
|
+
SkillRouteMap,
|
|
20
|
+
SkillRoutingFrontmatter,
|
|
21
|
+
} from "./types.ts";
|