@soleri/core 9.3.1 → 9.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/brain/intelligence.d.ts +5 -0
- package/dist/brain/intelligence.d.ts.map +1 -1
- package/dist/brain/intelligence.js +115 -26
- package/dist/brain/intelligence.js.map +1 -1
- package/dist/brain/learning-radar.d.ts +3 -3
- package/dist/brain/learning-radar.d.ts.map +1 -1
- package/dist/brain/learning-radar.js +8 -4
- package/dist/brain/learning-radar.js.map +1 -1
- package/dist/control/intent-router.d.ts +2 -2
- package/dist/control/intent-router.d.ts.map +1 -1
- package/dist/control/intent-router.js +35 -1
- package/dist/control/intent-router.js.map +1 -1
- package/dist/control/types.d.ts +10 -2
- package/dist/control/types.d.ts.map +1 -1
- package/dist/curator/curator.d.ts +4 -0
- package/dist/curator/curator.d.ts.map +1 -1
- package/dist/curator/curator.js +23 -1
- package/dist/curator/curator.js.map +1 -1
- package/dist/curator/schema.d.ts +1 -1
- package/dist/curator/schema.d.ts.map +1 -1
- package/dist/curator/schema.js +8 -0
- package/dist/curator/schema.js.map +1 -1
- package/dist/domain-packs/types.d.ts +6 -0
- package/dist/domain-packs/types.d.ts.map +1 -1
- package/dist/domain-packs/types.js +1 -0
- package/dist/domain-packs/types.js.map +1 -1
- package/dist/engine/module-manifest.js +3 -3
- package/dist/engine/module-manifest.js.map +1 -1
- package/dist/engine/register-engine.d.ts +9 -0
- package/dist/engine/register-engine.d.ts.map +1 -1
- package/dist/engine/register-engine.js +59 -1
- package/dist/engine/register-engine.js.map +1 -1
- package/dist/facades/types.d.ts +5 -1
- package/dist/facades/types.d.ts.map +1 -1
- package/dist/facades/types.js.map +1 -1
- package/dist/hooks/candidate-scorer.d.ts +28 -0
- package/dist/hooks/candidate-scorer.d.ts.map +1 -0
- package/dist/hooks/candidate-scorer.js +20 -0
- package/dist/hooks/candidate-scorer.js.map +1 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/operator/operator-context-store.d.ts +54 -0
- package/dist/operator/operator-context-store.d.ts.map +1 -0
- package/dist/operator/operator-context-store.js +434 -0
- package/dist/operator/operator-context-store.js.map +1 -0
- package/dist/operator/operator-context-types.d.ts +101 -0
- package/dist/operator/operator-context-types.d.ts.map +1 -0
- package/dist/operator/operator-context-types.js +27 -0
- package/dist/operator/operator-context-types.js.map +1 -0
- package/dist/packs/index.d.ts +2 -2
- package/dist/packs/index.d.ts.map +1 -1
- package/dist/packs/index.js +1 -1
- package/dist/packs/index.js.map +1 -1
- package/dist/packs/lockfile.d.ts +3 -0
- package/dist/packs/lockfile.d.ts.map +1 -1
- package/dist/packs/lockfile.js.map +1 -1
- package/dist/packs/types.d.ts +8 -2
- package/dist/packs/types.d.ts.map +1 -1
- package/dist/packs/types.js +6 -0
- package/dist/packs/types.js.map +1 -1
- package/dist/planning/plan-lifecycle.d.ts +12 -1
- package/dist/planning/plan-lifecycle.d.ts.map +1 -1
- package/dist/planning/plan-lifecycle.js +54 -16
- package/dist/planning/plan-lifecycle.js.map +1 -1
- package/dist/planning/planner-types.d.ts +6 -0
- package/dist/planning/planner-types.d.ts.map +1 -1
- package/dist/planning/planner.d.ts +21 -1
- package/dist/planning/planner.d.ts.map +1 -1
- package/dist/planning/planner.js +62 -3
- package/dist/planning/planner.js.map +1 -1
- package/dist/planning/task-complexity-assessor.d.ts.map +1 -1
- package/dist/planning/task-complexity-assessor.js.map +1 -1
- package/dist/plugins/types.d.ts +18 -18
- package/dist/runtime/admin-ops.d.ts +1 -1
- package/dist/runtime/admin-ops.d.ts.map +1 -1
- package/dist/runtime/admin-ops.js +100 -3
- package/dist/runtime/admin-ops.js.map +1 -1
- package/dist/runtime/admin-setup-ops.d.ts.map +1 -1
- package/dist/runtime/admin-setup-ops.js +19 -9
- package/dist/runtime/admin-setup-ops.js.map +1 -1
- package/dist/runtime/capture-ops.d.ts.map +1 -1
- package/dist/runtime/capture-ops.js +35 -7
- package/dist/runtime/capture-ops.js.map +1 -1
- package/dist/runtime/facades/brain-facade.d.ts.map +1 -1
- package/dist/runtime/facades/brain-facade.js +4 -2
- package/dist/runtime/facades/brain-facade.js.map +1 -1
- package/dist/runtime/facades/control-facade.d.ts.map +1 -1
- package/dist/runtime/facades/control-facade.js +8 -2
- package/dist/runtime/facades/control-facade.js.map +1 -1
- package/dist/runtime/facades/curator-facade.d.ts.map +1 -1
- package/dist/runtime/facades/curator-facade.js +13 -0
- package/dist/runtime/facades/curator-facade.js.map +1 -1
- package/dist/runtime/facades/memory-facade.d.ts.map +1 -1
- package/dist/runtime/facades/memory-facade.js +10 -12
- package/dist/runtime/facades/memory-facade.js.map +1 -1
- package/dist/runtime/facades/orchestrate-facade.d.ts.map +1 -1
- package/dist/runtime/facades/orchestrate-facade.js +36 -1
- package/dist/runtime/facades/orchestrate-facade.js.map +1 -1
- package/dist/runtime/facades/plan-facade.d.ts.map +1 -1
- package/dist/runtime/facades/plan-facade.js +20 -4
- package/dist/runtime/facades/plan-facade.js.map +1 -1
- package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
- package/dist/runtime/orchestrate-ops.js +71 -4
- package/dist/runtime/orchestrate-ops.js.map +1 -1
- package/dist/runtime/plan-feedback-helper.d.ts +21 -0
- package/dist/runtime/plan-feedback-helper.d.ts.map +1 -0
- package/dist/runtime/plan-feedback-helper.js +52 -0
- package/dist/runtime/plan-feedback-helper.js.map +1 -0
- package/dist/runtime/planning-extra-ops.d.ts.map +1 -1
- package/dist/runtime/planning-extra-ops.js +73 -34
- package/dist/runtime/planning-extra-ops.js.map +1 -1
- package/dist/runtime/session-briefing.d.ts.map +1 -1
- package/dist/runtime/session-briefing.js +9 -1
- package/dist/runtime/session-briefing.js.map +1 -1
- package/dist/runtime/types.d.ts +3 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/skills/sync-skills.d.ts.map +1 -1
- package/dist/skills/sync-skills.js +13 -7
- package/dist/skills/sync-skills.js.map +1 -1
- package/package.json +1 -1
- package/src/brain/brain-intelligence.test.ts +30 -0
- package/src/brain/brain.ts +120 -46
- package/src/brain/extraction-quality.test.ts +323 -0
- package/src/brain/intelligence.ts +175 -64
- package/src/brain/learning-radar.ts +8 -5
- package/src/brain/second-brain-features.test.ts +1 -1
- package/src/chat/agent-loop.ts +1 -1
- package/src/chat/notifications.ts +4 -0
- package/src/control/intent-router.test.ts +73 -3
- package/src/control/intent-router.ts +48 -9
- package/src/control/types.ts +13 -2
- package/src/curator/curator.test.ts +92 -0
- package/src/curator/curator.ts +162 -18
- package/src/curator/schema.ts +8 -0
- package/src/domain-packs/types.ts +8 -0
- package/src/engine/module-manifest.test.ts +8 -2
- package/src/engine/module-manifest.ts +3 -3
- package/src/engine/register-engine.test.ts +73 -1
- package/src/engine/register-engine.ts +61 -1
- package/src/facades/types.ts +5 -0
- package/src/hooks/candidate-scorer.test.ts +76 -0
- package/src/hooks/candidate-scorer.ts +39 -0
- package/src/hooks/index.ts +6 -0
- package/src/index.ts +24 -0
- package/src/llm/llm-client.ts +1 -0
- package/src/operator/operator-context-store.test.ts +698 -0
- package/src/operator/operator-context-store.ts +569 -0
- package/src/operator/operator-context-types.ts +139 -0
- package/src/packs/index.ts +3 -1
- package/src/packs/lockfile.ts +3 -0
- package/src/packs/types.ts +9 -0
- package/src/persistence/sqlite-provider.ts +1 -0
- package/src/planning/github-projection.ts +48 -44
- package/src/planning/plan-lifecycle.ts +93 -22
- package/src/planning/planner-types.ts +6 -0
- package/src/planning/planner.ts +74 -4
- package/src/planning/task-complexity-assessor.test.ts +6 -2
- package/src/planning/task-complexity-assessor.ts +1 -4
- package/src/queue/pipeline-runner.ts +4 -0
- package/src/runtime/admin-ops.test.ts +139 -6
- package/src/runtime/admin-ops.ts +104 -3
- package/src/runtime/admin-setup-ops.ts +30 -10
- package/src/runtime/capture-ops.test.ts +84 -0
- package/src/runtime/capture-ops.ts +35 -7
- package/src/runtime/curator-extra-ops.test.ts +7 -0
- package/src/runtime/curator-extra-ops.ts +10 -1
- package/src/runtime/facades/admin-facade.test.ts +1 -1
- package/src/runtime/facades/brain-facade.ts +6 -3
- package/src/runtime/facades/control-facade.ts +10 -2
- package/src/runtime/facades/curator-facade.test.ts +7 -0
- package/src/runtime/facades/curator-facade.ts +18 -0
- package/src/runtime/facades/memory-facade.test.ts +14 -12
- package/src/runtime/facades/memory-facade.ts +197 -12
- package/src/runtime/facades/orchestrate-facade.ts +33 -1
- package/src/runtime/facades/plan-facade.test.ts +213 -0
- package/src/runtime/facades/plan-facade.ts +23 -4
- package/src/runtime/orchestrate-ops.test.ts +202 -2
- package/src/runtime/orchestrate-ops.ts +88 -7
- package/src/runtime/plan-feedback-helper.test.ts +173 -0
- package/src/runtime/plan-feedback-helper.ts +63 -0
- package/src/runtime/planning-extra-ops.test.ts +43 -1
- package/src/runtime/planning-extra-ops.ts +96 -33
- package/src/runtime/runtime.test.ts +50 -2
- package/src/runtime/runtime.ts +117 -89
- package/src/runtime/session-briefing.test.ts +1 -0
- package/src/runtime/session-briefing.ts +10 -1
- package/src/runtime/shutdown-registry.test.ts +151 -0
- package/src/runtime/shutdown-registry.ts +85 -0
- package/src/runtime/types.ts +7 -1
- package/src/skills/sync-skills.ts +14 -7
- package/src/transport/http-server.ts +50 -3
- package/src/transport/ws-server.ts +8 -0
- package/src/vault/linking.test.ts +12 -0
- package/src/vault/linking.ts +90 -44
- package/src/vault/vault-maintenance.ts +11 -18
- package/src/vault/vault-memories.ts +21 -13
- package/src/vault/vault-schema.ts +21 -0
- package/src/vault/vault.ts +8 -3
- package/vitest.config.ts +1 -0
package/src/packs/index.ts
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
export {
|
|
6
6
|
packManifestSchema,
|
|
7
|
+
PACK_TIERS,
|
|
7
8
|
type PackManifest,
|
|
9
|
+
type PackTier as ManifestPackTier,
|
|
8
10
|
type PackStatus,
|
|
9
11
|
type InstalledPack,
|
|
10
12
|
type InstallResult,
|
|
@@ -14,7 +16,7 @@ export {
|
|
|
14
16
|
export { PackInstaller } from './pack-installer.js';
|
|
15
17
|
|
|
16
18
|
export { PackLockfile, inferPackType } from './lockfile.js';
|
|
17
|
-
export type { LockEntry, PackType, PackSource, LockfileData } from './lockfile.js';
|
|
19
|
+
export type { LockEntry, PackType, PackSource, PackTier, LockfileData } from './lockfile.js';
|
|
18
20
|
|
|
19
21
|
export { resolvePack, checkNpmVersion, checkVersionCompat } from './resolver.js';
|
|
20
22
|
export type { ResolvedPack, ResolveOptions } from './resolver.js';
|
package/src/packs/lockfile.ts
CHANGED
|
@@ -39,10 +39,13 @@ export interface LockEntry {
|
|
|
39
39
|
facadesRegistered: boolean;
|
|
40
40
|
/** Compatible @soleri/core version range (from manifest "soleri" field) */
|
|
41
41
|
soleriRange?: string;
|
|
42
|
+
/** Pack tier: default (ships with engine), community (free), premium (unlocked today) */
|
|
43
|
+
tier?: PackTier;
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
export type PackType = 'hooks' | 'skills' | 'knowledge' | 'domain' | 'bundle';
|
|
45
47
|
export type PackSource = 'built-in' | 'local' | 'npm';
|
|
48
|
+
export type PackTier = 'default' | 'community' | 'premium';
|
|
46
49
|
|
|
47
50
|
export interface LockfileData {
|
|
48
51
|
/** Lockfile format version */
|
package/src/packs/types.ts
CHANGED
|
@@ -23,11 +23,20 @@ import { z } from 'zod';
|
|
|
23
23
|
// MANIFEST SCHEMA
|
|
24
24
|
// =============================================================================
|
|
25
25
|
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Pack Tiers — determines visibility, licensing, and install behavior
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
export const PACK_TIERS = ['default', 'community', 'premium'] as const;
|
|
31
|
+
export type PackTier = (typeof PACK_TIERS)[number];
|
|
32
|
+
|
|
26
33
|
export const packManifestSchema = z.object({
|
|
27
34
|
id: z.string().regex(/^[a-z0-9-]+$/, 'Pack ID must be lowercase alphanumeric with hyphens'),
|
|
28
35
|
name: z.string().min(1),
|
|
29
36
|
version: z.string().regex(/^\d+\.\d+\.\d+$/, 'Version must be semver (x.y.z)'),
|
|
30
37
|
description: z.string().optional().default(''),
|
|
38
|
+
/** Pack tier: 'default' (ships with engine), 'community' (free, npm), 'premium' (unlocked today, gated later) */
|
|
39
|
+
tier: z.enum(PACK_TIERS).optional().default('community'),
|
|
31
40
|
/** Domains this pack covers */
|
|
32
41
|
domains: z.array(z.string()).optional().default([]),
|
|
33
42
|
/** Minimum engine version required (semver range) */
|
|
@@ -20,6 +20,7 @@ export function applyPerformancePragmas(db: Database.Database): void {
|
|
|
20
20
|
db.pragma('cache_size = -64000'); // 64MB
|
|
21
21
|
db.pragma('temp_store = MEMORY');
|
|
22
22
|
db.pragma('mmap_size = 268435456'); // 256MB
|
|
23
|
+
db.pragma('synchronous = NORMAL');
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
export class SQLitePersistenceProvider implements PersistenceProvider {
|
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
* The plan is the source of truth; GitHub issues are the projection.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { execFile } from 'node:child_process';
|
|
11
|
+
import { promisify } from 'node:util';
|
|
12
|
+
|
|
13
|
+
const execFileAsync = promisify(execFile);
|
|
11
14
|
|
|
12
15
|
// ---------------------------------------------------------------------------
|
|
13
16
|
// Types
|
|
@@ -88,15 +91,14 @@ export function parseGitHubRemote(remoteUrl: string): GitHubRepo | null {
|
|
|
88
91
|
* Detect the GitHub remote from a project directory.
|
|
89
92
|
* Returns null if no GitHub remote found or not a git repo.
|
|
90
93
|
*/
|
|
91
|
-
export function detectGitHubRemote(projectPath: string): GitHubRepo | null {
|
|
94
|
+
export async function detectGitHubRemote(projectPath: string): Promise<GitHubRepo | null> {
|
|
92
95
|
try {
|
|
93
|
-
const
|
|
96
|
+
const { stdout } = await execFileAsync('git', ['remote', 'get-url', 'origin'], {
|
|
94
97
|
cwd: projectPath,
|
|
95
|
-
encoding: 'utf-8',
|
|
96
98
|
timeout: 5000,
|
|
97
|
-
|
|
98
|
-
})
|
|
99
|
-
return parseGitHubRemote(
|
|
99
|
+
signal: AbortSignal.timeout(5000),
|
|
100
|
+
});
|
|
101
|
+
return parseGitHubRemote(stdout.trim());
|
|
100
102
|
} catch {
|
|
101
103
|
return null;
|
|
102
104
|
}
|
|
@@ -105,12 +107,11 @@ export function detectGitHubRemote(projectPath: string): GitHubRepo | null {
|
|
|
105
107
|
/**
|
|
106
108
|
* Check if the `gh` CLI is authenticated.
|
|
107
109
|
*/
|
|
108
|
-
export function isGhAuthenticated(): boolean {
|
|
110
|
+
export async function isGhAuthenticated(): Promise<boolean> {
|
|
109
111
|
try {
|
|
110
|
-
|
|
111
|
-
encoding: 'utf-8',
|
|
112
|
+
await execFileAsync('gh', ['auth', 'status'], {
|
|
112
113
|
timeout: 5000,
|
|
113
|
-
|
|
114
|
+
signal: AbortSignal.timeout(5000),
|
|
114
115
|
});
|
|
115
116
|
return true;
|
|
116
117
|
} catch {
|
|
@@ -125,9 +126,9 @@ export function isGhAuthenticated(): boolean {
|
|
|
125
126
|
/**
|
|
126
127
|
* List milestones for a GitHub repo.
|
|
127
128
|
*/
|
|
128
|
-
export function listMilestones(repo: GitHubRepo): GitHubMilestone[] {
|
|
129
|
+
export async function listMilestones(repo: GitHubRepo): Promise<GitHubMilestone[]> {
|
|
129
130
|
try {
|
|
130
|
-
const
|
|
131
|
+
const { stdout } = await execFileAsync(
|
|
131
132
|
'gh',
|
|
132
133
|
[
|
|
133
134
|
'api',
|
|
@@ -136,12 +137,12 @@ export function listMilestones(repo: GitHubRepo): GitHubMilestone[] {
|
|
|
136
137
|
'.[] | {number, title, state}',
|
|
137
138
|
],
|
|
138
139
|
{
|
|
139
|
-
encoding: 'utf-8',
|
|
140
140
|
timeout: 10000,
|
|
141
|
-
|
|
141
|
+
signal: AbortSignal.timeout(10000),
|
|
142
142
|
},
|
|
143
|
-
)
|
|
143
|
+
);
|
|
144
144
|
|
|
145
|
+
const output = stdout.trim();
|
|
145
146
|
if (!output) return [];
|
|
146
147
|
|
|
147
148
|
// gh --jq outputs one JSON object per line
|
|
@@ -157,9 +158,12 @@ export function listMilestones(repo: GitHubRepo): GitHubMilestone[] {
|
|
|
157
158
|
/**
|
|
158
159
|
* List open issues for a GitHub repo.
|
|
159
160
|
*/
|
|
160
|
-
export function listOpenIssues(
|
|
161
|
+
export async function listOpenIssues(
|
|
162
|
+
repo: GitHubRepo,
|
|
163
|
+
limit: number = 100,
|
|
164
|
+
): Promise<GitHubIssue[]> {
|
|
161
165
|
try {
|
|
162
|
-
const
|
|
166
|
+
const { stdout } = await execFileAsync(
|
|
163
167
|
'gh',
|
|
164
168
|
[
|
|
165
169
|
'issue',
|
|
@@ -174,12 +178,12 @@ export function listOpenIssues(repo: GitHubRepo, limit: number = 100): GitHubIss
|
|
|
174
178
|
'number,title,state,body',
|
|
175
179
|
],
|
|
176
180
|
{
|
|
177
|
-
encoding: 'utf-8',
|
|
178
181
|
timeout: 10000,
|
|
179
|
-
|
|
182
|
+
signal: AbortSignal.timeout(10000),
|
|
180
183
|
},
|
|
181
|
-
)
|
|
184
|
+
);
|
|
182
185
|
|
|
186
|
+
const output = stdout.trim();
|
|
183
187
|
if (!output) return [];
|
|
184
188
|
return JSON.parse(output) as GitHubIssue[];
|
|
185
189
|
} catch {
|
|
@@ -190,18 +194,18 @@ export function listOpenIssues(repo: GitHubRepo, limit: number = 100): GitHubIss
|
|
|
190
194
|
/**
|
|
191
195
|
* List labels for a GitHub repo.
|
|
192
196
|
*/
|
|
193
|
-
export function listLabels(repo: GitHubRepo): GitHubLabel[] {
|
|
197
|
+
export async function listLabels(repo: GitHubRepo): Promise<GitHubLabel[]> {
|
|
194
198
|
try {
|
|
195
|
-
const
|
|
199
|
+
const { stdout } = await execFileAsync(
|
|
196
200
|
'gh',
|
|
197
201
|
['label', 'list', '--repo', `${repo.owner}/${repo.repo}`, '--json', 'name,color'],
|
|
198
202
|
{
|
|
199
|
-
encoding: 'utf-8',
|
|
200
203
|
timeout: 10000,
|
|
201
|
-
|
|
204
|
+
signal: AbortSignal.timeout(10000),
|
|
202
205
|
},
|
|
203
|
-
)
|
|
206
|
+
);
|
|
204
207
|
|
|
208
|
+
const output = stdout.trim();
|
|
205
209
|
if (!output) return [];
|
|
206
210
|
return JSON.parse(output) as GitHubLabel[];
|
|
207
211
|
} catch {
|
|
@@ -217,16 +221,18 @@ export function listLabels(repo: GitHubRepo): GitHubLabel[] {
|
|
|
217
221
|
* Detect full GitHub context for a project.
|
|
218
222
|
* Returns null if not a GitHub project or gh CLI not available.
|
|
219
223
|
*/
|
|
220
|
-
export function detectGitHubContext(projectPath: string): GitHubContext | null {
|
|
221
|
-
const repo = detectGitHubRemote(projectPath);
|
|
224
|
+
export async function detectGitHubContext(projectPath: string): Promise<GitHubContext | null> {
|
|
225
|
+
const repo = await detectGitHubRemote(projectPath);
|
|
222
226
|
if (!repo) return null;
|
|
223
227
|
|
|
224
|
-
const authenticated = isGhAuthenticated();
|
|
228
|
+
const authenticated = await isGhAuthenticated();
|
|
225
229
|
if (!authenticated) return null;
|
|
226
230
|
|
|
227
|
-
const milestones =
|
|
228
|
-
|
|
229
|
-
|
|
231
|
+
const [milestones, existingIssues, labels] = await Promise.all([
|
|
232
|
+
listMilestones(repo),
|
|
233
|
+
listOpenIssues(repo),
|
|
234
|
+
listLabels(repo),
|
|
235
|
+
]);
|
|
230
236
|
|
|
231
237
|
return { repo, authenticated, milestones, existingIssues, labels };
|
|
232
238
|
}
|
|
@@ -370,7 +376,7 @@ export function formatIssueBody(
|
|
|
370
376
|
* Create a GitHub issue using the `gh` CLI.
|
|
371
377
|
* Returns the issue number, or null on failure.
|
|
372
378
|
*/
|
|
373
|
-
export function createGitHubIssue(
|
|
379
|
+
export async function createGitHubIssue(
|
|
374
380
|
repo: GitHubRepo,
|
|
375
381
|
title: string,
|
|
376
382
|
body: string,
|
|
@@ -378,7 +384,7 @@ export function createGitHubIssue(
|
|
|
378
384
|
milestone?: number;
|
|
379
385
|
labels?: string[];
|
|
380
386
|
},
|
|
381
|
-
): number | null {
|
|
387
|
+
): Promise<number | null> {
|
|
382
388
|
try {
|
|
383
389
|
const args = [
|
|
384
390
|
'issue',
|
|
@@ -399,14 +405,13 @@ export function createGitHubIssue(
|
|
|
399
405
|
args.push('--label', options.labels.join(','));
|
|
400
406
|
}
|
|
401
407
|
|
|
402
|
-
const
|
|
403
|
-
encoding: 'utf-8',
|
|
408
|
+
const { stdout } = await execFileAsync('gh', args, {
|
|
404
409
|
timeout: 15000,
|
|
405
|
-
|
|
406
|
-
})
|
|
410
|
+
signal: AbortSignal.timeout(15000),
|
|
411
|
+
});
|
|
407
412
|
|
|
408
413
|
// gh issue create returns the issue URL: https://github.com/owner/repo/issues/123
|
|
409
|
-
const match =
|
|
414
|
+
const match = stdout.trim().match(/\/issues\/(\d+)/);
|
|
410
415
|
return match ? parseInt(match[1], 10) : null;
|
|
411
416
|
} catch {
|
|
412
417
|
return null;
|
|
@@ -416,13 +421,13 @@ export function createGitHubIssue(
|
|
|
416
421
|
/**
|
|
417
422
|
* Update an existing GitHub issue body (for linking plans to existing issues).
|
|
418
423
|
*/
|
|
419
|
-
export function updateGitHubIssueBody(
|
|
424
|
+
export async function updateGitHubIssueBody(
|
|
420
425
|
repo: GitHubRepo,
|
|
421
426
|
issueNumber: number,
|
|
422
427
|
body: string,
|
|
423
|
-
): boolean {
|
|
428
|
+
): Promise<boolean> {
|
|
424
429
|
try {
|
|
425
|
-
|
|
430
|
+
await execFileAsync(
|
|
426
431
|
'gh',
|
|
427
432
|
[
|
|
428
433
|
'issue',
|
|
@@ -434,9 +439,8 @@ export function updateGitHubIssueBody(
|
|
|
434
439
|
body,
|
|
435
440
|
],
|
|
436
441
|
{
|
|
437
|
-
encoding: 'utf-8',
|
|
438
442
|
timeout: 15000,
|
|
439
|
-
|
|
443
|
+
signal: AbortSignal.timeout(15000),
|
|
440
444
|
},
|
|
441
445
|
);
|
|
442
446
|
return true;
|
|
@@ -232,7 +232,13 @@ export interface IterateChanges {
|
|
|
232
232
|
objective?: string;
|
|
233
233
|
scope?: string;
|
|
234
234
|
decisions?: (string | PlanDecision)[];
|
|
235
|
-
addTasks?: Array<{
|
|
235
|
+
addTasks?: Array<{
|
|
236
|
+
title: string;
|
|
237
|
+
description: string;
|
|
238
|
+
phase?: string;
|
|
239
|
+
milestone?: string;
|
|
240
|
+
parentTaskId?: string;
|
|
241
|
+
}>;
|
|
236
242
|
removeTasks?: string[];
|
|
237
243
|
approach?: string;
|
|
238
244
|
context?: string;
|
|
@@ -240,26 +246,61 @@ export interface IterateChanges {
|
|
|
240
246
|
tool_chain?: string[];
|
|
241
247
|
flow?: string;
|
|
242
248
|
target_mode?: string;
|
|
249
|
+
alternatives?: import('./planner-types.js').PlanAlternative[];
|
|
243
250
|
}
|
|
244
251
|
|
|
245
252
|
/**
|
|
246
253
|
* Apply iteration changes to a plan (mutates in place).
|
|
247
254
|
* Caller is responsible for status validation and persistence.
|
|
255
|
+
* Returns the number of fields actually mutated (0 = no-op).
|
|
248
256
|
*/
|
|
249
|
-
export function applyIteration(plan: Plan, changes: IterateChanges):
|
|
257
|
+
export function applyIteration(plan: Plan, changes: IterateChanges): number {
|
|
250
258
|
const now = Date.now();
|
|
251
|
-
|
|
252
|
-
if (changes.
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
if (changes.
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
259
|
+
let mutated = 0;
|
|
260
|
+
if (changes.objective !== undefined) {
|
|
261
|
+
plan.objective = changes.objective;
|
|
262
|
+
mutated++;
|
|
263
|
+
}
|
|
264
|
+
if (changes.scope !== undefined) {
|
|
265
|
+
plan.scope = changes.scope;
|
|
266
|
+
mutated++;
|
|
267
|
+
}
|
|
268
|
+
if (changes.decisions !== undefined) {
|
|
269
|
+
plan.decisions = changes.decisions;
|
|
270
|
+
mutated++;
|
|
271
|
+
}
|
|
272
|
+
if (changes.approach !== undefined) {
|
|
273
|
+
plan.approach = changes.approach;
|
|
274
|
+
mutated++;
|
|
275
|
+
}
|
|
276
|
+
if (changes.context !== undefined) {
|
|
277
|
+
plan.context = changes.context;
|
|
278
|
+
mutated++;
|
|
279
|
+
}
|
|
280
|
+
if (changes.success_criteria !== undefined) {
|
|
281
|
+
plan.success_criteria = changes.success_criteria;
|
|
282
|
+
mutated++;
|
|
283
|
+
}
|
|
284
|
+
if (changes.tool_chain !== undefined) {
|
|
285
|
+
plan.tool_chain = changes.tool_chain;
|
|
286
|
+
mutated++;
|
|
287
|
+
}
|
|
288
|
+
if (changes.flow !== undefined) {
|
|
289
|
+
plan.flow = changes.flow;
|
|
290
|
+
mutated++;
|
|
291
|
+
}
|
|
292
|
+
if (changes.target_mode !== undefined) {
|
|
293
|
+
plan.target_mode = changes.target_mode;
|
|
294
|
+
mutated++;
|
|
295
|
+
}
|
|
296
|
+
if (changes.alternatives !== undefined) {
|
|
297
|
+
plan.alternatives = changes.alternatives;
|
|
298
|
+
mutated++;
|
|
299
|
+
}
|
|
260
300
|
if (changes.removeTasks?.length) {
|
|
261
301
|
const removeSet = new Set(changes.removeTasks);
|
|
262
302
|
plan.tasks = plan.tasks.filter((t) => !removeSet.has(t.id));
|
|
303
|
+
mutated++;
|
|
263
304
|
}
|
|
264
305
|
if (changes.addTasks?.length) {
|
|
265
306
|
const maxIndex = plan.tasks.reduce((max, t) => {
|
|
@@ -267,16 +308,24 @@ export function applyIteration(plan: Plan, changes: IterateChanges): void {
|
|
|
267
308
|
return isNaN(num) ? max : Math.max(max, num);
|
|
268
309
|
}, 0);
|
|
269
310
|
for (let i = 0; i < changes.addTasks.length; i++) {
|
|
311
|
+
const t = changes.addTasks[i];
|
|
270
312
|
plan.tasks.push({
|
|
271
313
|
id: `task-${maxIndex + i + 1}`,
|
|
272
|
-
title:
|
|
273
|
-
description:
|
|
314
|
+
title: t.title,
|
|
315
|
+
description: t.description,
|
|
274
316
|
status: 'pending' as TaskStatus,
|
|
317
|
+
...(t.phase !== undefined && { phase: t.phase }),
|
|
318
|
+
...(t.milestone !== undefined && { milestone: t.milestone }),
|
|
319
|
+
...(t.parentTaskId !== undefined && { parentTaskId: t.parentTaskId }),
|
|
275
320
|
updatedAt: now,
|
|
276
321
|
});
|
|
277
322
|
}
|
|
323
|
+
mutated++;
|
|
278
324
|
}
|
|
279
|
-
|
|
325
|
+
if (mutated > 0) {
|
|
326
|
+
plan.updatedAt = now;
|
|
327
|
+
}
|
|
328
|
+
return mutated;
|
|
280
329
|
}
|
|
281
330
|
|
|
282
331
|
/**
|
|
@@ -308,7 +357,13 @@ export function createPlanObject(params: {
|
|
|
308
357
|
objective: string;
|
|
309
358
|
scope: string;
|
|
310
359
|
decisions?: (string | PlanDecision)[];
|
|
311
|
-
tasks?: Array<{
|
|
360
|
+
tasks?: Array<{
|
|
361
|
+
title: string;
|
|
362
|
+
description: string;
|
|
363
|
+
phase?: string;
|
|
364
|
+
milestone?: string;
|
|
365
|
+
parentTaskId?: string;
|
|
366
|
+
}>;
|
|
312
367
|
approach?: string;
|
|
313
368
|
context?: string;
|
|
314
369
|
success_criteria?: string[];
|
|
@@ -325,13 +380,20 @@ export function createPlanObject(params: {
|
|
|
325
380
|
scope: params.scope,
|
|
326
381
|
status: params.initialStatus ?? 'draft',
|
|
327
382
|
decisions: params.decisions ?? [],
|
|
328
|
-
tasks: (params.tasks ?? []).map((t, i) =>
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
383
|
+
tasks: (params.tasks ?? []).map((t, i) =>
|
|
384
|
+
Object.assign(
|
|
385
|
+
{
|
|
386
|
+
id: `task-${i + 1}`,
|
|
387
|
+
title: t.title,
|
|
388
|
+
description: t.description,
|
|
389
|
+
status: `pending` as TaskStatus,
|
|
390
|
+
},
|
|
391
|
+
t.phase !== undefined && { phase: t.phase },
|
|
392
|
+
t.milestone !== undefined && { milestone: t.milestone },
|
|
393
|
+
t.parentTaskId !== undefined && { parentTaskId: t.parentTaskId },
|
|
394
|
+
{ updatedAt: now },
|
|
395
|
+
),
|
|
396
|
+
),
|
|
335
397
|
...(params.approach !== undefined && { approach: params.approach }),
|
|
336
398
|
...(params.context !== undefined && { context: params.context }),
|
|
337
399
|
...(params.success_criteria !== undefined && { success_criteria: params.success_criteria }),
|
|
@@ -352,6 +414,9 @@ export function applySplitTasks(
|
|
|
352
414
|
description: string;
|
|
353
415
|
dependsOn?: string[];
|
|
354
416
|
acceptanceCriteria?: string[];
|
|
417
|
+
phase?: string;
|
|
418
|
+
milestone?: string;
|
|
419
|
+
parentTaskId?: string;
|
|
355
420
|
}>,
|
|
356
421
|
): void {
|
|
357
422
|
const now = Date.now();
|
|
@@ -362,6 +427,9 @@ export function applySplitTasks(
|
|
|
362
427
|
status: 'pending' as TaskStatus,
|
|
363
428
|
dependsOn: t.dependsOn,
|
|
364
429
|
...(t.acceptanceCriteria && { acceptanceCriteria: t.acceptanceCriteria }),
|
|
430
|
+
...(t.phase !== undefined && { phase: t.phase }),
|
|
431
|
+
...(t.milestone !== undefined && { milestone: t.milestone }),
|
|
432
|
+
...(t.parentTaskId !== undefined && { parentTaskId: t.parentTaskId }),
|
|
365
433
|
updatedAt: now,
|
|
366
434
|
}));
|
|
367
435
|
const taskIds = new Set(plan.tasks.map((t) => t.id));
|
|
@@ -372,6 +440,9 @@ export function applySplitTasks(
|
|
|
372
440
|
throw new Error(`Task '${task.id}' depends on unknown task '${dep}'`);
|
|
373
441
|
}
|
|
374
442
|
}
|
|
443
|
+
if (task.parentTaskId && !taskIds.has(task.parentTaskId)) {
|
|
444
|
+
throw new Error(`Task '${task.id}' references unknown parent task '${task.parentTaskId}'`);
|
|
445
|
+
}
|
|
375
446
|
}
|
|
376
447
|
plan.updatedAt = now;
|
|
377
448
|
}
|
|
@@ -66,6 +66,12 @@ export interface PlanTask {
|
|
|
66
66
|
status: TaskStatus;
|
|
67
67
|
/** Optional dependency IDs — tasks that must complete before this one. */
|
|
68
68
|
dependsOn?: string[];
|
|
69
|
+
/** Phase this task belongs to (e.g., "wave-1", "discovery", "implementation"). */
|
|
70
|
+
phase?: string;
|
|
71
|
+
/** Milestone this task contributes to (e.g., "v1.0", "mvp", "beta"). */
|
|
72
|
+
milestone?: string;
|
|
73
|
+
/** Parent task ID — enables sub-task hierarchy within a plan. */
|
|
74
|
+
parentTaskId?: string;
|
|
69
75
|
/** Evidence submitted for task acceptance criteria. */
|
|
70
76
|
evidence?: TaskEvidence[];
|
|
71
77
|
/** Whether this task has been verified (all evidence checked + reviews passed). */
|
package/src/planning/planner.ts
CHANGED
|
@@ -235,15 +235,17 @@ export class Planner {
|
|
|
235
235
|
);
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
-
iterate(planId: string, changes: IterateChanges): Plan {
|
|
238
|
+
iterate(planId: string, changes: IterateChanges): { plan: Plan; mutated: number } {
|
|
239
239
|
const plan = this.requirePlan(planId);
|
|
240
240
|
if (plan.status !== 'draft' && plan.status !== 'brainstorming')
|
|
241
241
|
throw new Error(
|
|
242
242
|
`Cannot iterate plan in '${plan.status}' status — must be 'draft' or 'brainstorming'`,
|
|
243
243
|
);
|
|
244
|
-
applyIteration(plan, changes);
|
|
245
|
-
|
|
246
|
-
|
|
244
|
+
const mutated = applyIteration(plan, changes);
|
|
245
|
+
if (mutated > 0) {
|
|
246
|
+
this.save();
|
|
247
|
+
}
|
|
248
|
+
return { plan, mutated };
|
|
247
249
|
}
|
|
248
250
|
|
|
249
251
|
splitTasks(
|
|
@@ -253,6 +255,9 @@ export class Planner {
|
|
|
253
255
|
description: string;
|
|
254
256
|
dependsOn?: string[];
|
|
255
257
|
acceptanceCriteria?: string[];
|
|
258
|
+
phase?: string;
|
|
259
|
+
milestone?: string;
|
|
260
|
+
parentTaskId?: string;
|
|
256
261
|
}>,
|
|
257
262
|
): Plan {
|
|
258
263
|
const plan = this.requirePlan(planId);
|
|
@@ -445,6 +450,71 @@ export class Planner {
|
|
|
445
450
|
return toArchive;
|
|
446
451
|
}
|
|
447
452
|
|
|
453
|
+
/**
|
|
454
|
+
* Close stale plans — plans in non-terminal states older than the given threshold.
|
|
455
|
+
* For draft/approved: uses 30 min TTL by default.
|
|
456
|
+
* For executing/reconciling: uses olderThanMs parameter (24h default).
|
|
457
|
+
* Returns the list of closed plan IDs.
|
|
458
|
+
*/
|
|
459
|
+
closeStale(olderThanMs?: number): {
|
|
460
|
+
closedIds: string[];
|
|
461
|
+
closedPlans: Array<{ id: string; previousStatus: string; reason: string }>;
|
|
462
|
+
} {
|
|
463
|
+
const now = Date.now();
|
|
464
|
+
const forceAll = olderThanMs === 0;
|
|
465
|
+
const defaultTtl = forceAll ? 0 : 30 * 60 * 1000; // 30 minutes for draft/approved
|
|
466
|
+
const executingTtl = forceAll ? 0 : (olderThanMs ?? 24 * 60 * 60 * 1000); // 24h default for executing/reconciling
|
|
467
|
+
const closed: Array<{ id: string; previousStatus: string; reason: string }> = [];
|
|
468
|
+
|
|
469
|
+
for (const plan of this.store.plans) {
|
|
470
|
+
// Skip terminal states
|
|
471
|
+
if (plan.status === 'completed' || plan.status === 'archived') continue;
|
|
472
|
+
|
|
473
|
+
const age = now - plan.updatedAt;
|
|
474
|
+
let shouldClose = false;
|
|
475
|
+
let reason = '';
|
|
476
|
+
|
|
477
|
+
if (plan.status === 'draft' || plan.status === 'approved') {
|
|
478
|
+
// Short TTL for draft/approved — these should move quickly
|
|
479
|
+
if (age >= defaultTtl) {
|
|
480
|
+
shouldClose = true;
|
|
481
|
+
reason = `ttl-expired (${plan.status}, age: ${Math.round(age / 60000)}min)`;
|
|
482
|
+
}
|
|
483
|
+
} else if (
|
|
484
|
+
plan.status === 'executing' ||
|
|
485
|
+
plan.status === 'validating' ||
|
|
486
|
+
plan.status === 'reconciling' ||
|
|
487
|
+
plan.status === 'brainstorming'
|
|
488
|
+
) {
|
|
489
|
+
// Longer TTL for active states
|
|
490
|
+
if (age >= executingTtl) {
|
|
491
|
+
shouldClose = true;
|
|
492
|
+
reason = `stale-closed (${plan.status}, age: ${Math.round(age / 3600000)}h)`;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (shouldClose) {
|
|
497
|
+
const previousStatus = plan.status;
|
|
498
|
+
// Force transition to completed (bypass FSM since these are stale)
|
|
499
|
+
plan.status = 'completed';
|
|
500
|
+
plan.updatedAt = now;
|
|
501
|
+
if (!plan.reconciliation) {
|
|
502
|
+
plan.reconciliation = {
|
|
503
|
+
planId: plan.id,
|
|
504
|
+
accuracy: 0,
|
|
505
|
+
driftItems: [],
|
|
506
|
+
summary: `Auto-closed: ${reason}`,
|
|
507
|
+
reconciledAt: now,
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
closed.push({ id: plan.id, previousStatus, reason });
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (closed.length > 0) this.save();
|
|
515
|
+
return { closedIds: closed.map((c) => c.id), closedPlans: closed };
|
|
516
|
+
}
|
|
517
|
+
|
|
448
518
|
stats(): {
|
|
449
519
|
total: number;
|
|
450
520
|
byStatus: Record<PlanStatus, number>;
|
|
@@ -72,7 +72,10 @@ describe('assessTaskComplexity — complex tasks', () => {
|
|
|
72
72
|
});
|
|
73
73
|
|
|
74
74
|
it('classifies many-file task with design decision as complex', () => {
|
|
75
|
-
const result = assess({
|
|
75
|
+
const result = assess({
|
|
76
|
+
prompt: 'how should we update styles across the app',
|
|
77
|
+
filesEstimated: 5,
|
|
78
|
+
});
|
|
76
79
|
expect(result.classification).toBe('complex');
|
|
77
80
|
expect(result.score).toBeGreaterThanOrEqual(40);
|
|
78
81
|
expect(signalByName(result, 'file-count')!.triggered).toBe(true);
|
|
@@ -117,7 +120,8 @@ describe('assessTaskComplexity — edge cases', () => {
|
|
|
117
120
|
|
|
118
121
|
it('clamps score to 100 maximum', () => {
|
|
119
122
|
const result = assess({
|
|
120
|
-
prompt:
|
|
123
|
+
prompt:
|
|
124
|
+
'add authentication, migrate the DB, install new package, how should we design this, refactor across all modules',
|
|
121
125
|
filesEstimated: 10,
|
|
122
126
|
domains: ['vault', 'brain', 'planning'],
|
|
123
127
|
});
|
|
@@ -164,10 +164,7 @@ export function assessTaskComplexity(input: AssessmentInput): AssessmentResult {
|
|
|
164
164
|
detectMultiDomain(input),
|
|
165
165
|
];
|
|
166
166
|
|
|
167
|
-
const rawScore = signals.reduce(
|
|
168
|
-
(sum, s) => sum + (s.triggered ? s.weight : 0),
|
|
169
|
-
0,
|
|
170
|
-
);
|
|
167
|
+
const rawScore = signals.reduce((sum, s) => sum + (s.triggered ? s.weight : 0), 0);
|
|
171
168
|
|
|
172
169
|
// Clamp to 0-100
|
|
173
170
|
const score = Math.max(0, Math.min(100, rawScore));
|
|
@@ -58,6 +58,10 @@ export class PipelineRunner {
|
|
|
58
58
|
if (this.running) return;
|
|
59
59
|
this.running = true;
|
|
60
60
|
this.timer = setInterval(() => this.tick(), this.pollIntervalMs);
|
|
61
|
+
// Don't prevent process exit for background polling
|
|
62
|
+
if (this.timer && typeof this.timer === 'object' && 'unref' in this.timer) {
|
|
63
|
+
(this.timer as NodeJS.Timeout).unref();
|
|
64
|
+
}
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
/**
|