pi-continuous-learning 0.6.0 → 0.7.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/README.md +78 -0
- package/dist/agents-md.d.ts +23 -2
- package/dist/agents-md.d.ts.map +1 -1
- package/dist/agents-md.js +58 -3
- package/dist/agents-md.js.map +1 -1
- package/dist/cli/analyze-single-shot.d.ts +8 -2
- package/dist/cli/analyze-single-shot.d.ts.map +1 -1
- package/dist/cli/analyze-single-shot.js +25 -3
- package/dist/cli/analyze-single-shot.js.map +1 -1
- package/dist/cli/analyze.js +20 -8
- package/dist/cli/analyze.js.map +1 -1
- package/dist/command-scaffold.d.ts +25 -0
- package/dist/command-scaffold.d.ts.map +1 -0
- package/dist/command-scaffold.js +77 -0
- package/dist/command-scaffold.js.map +1 -0
- package/dist/confidence.d.ts.map +1 -1
- package/dist/confidence.js +2 -1
- package/dist/confidence.js.map +1 -1
- package/dist/config.d.ts +16 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +31 -0
- package/dist/config.js.map +1 -1
- package/dist/graduation.d.ts +63 -0
- package/dist/graduation.d.ts.map +1 -0
- package/dist/graduation.js +155 -0
- package/dist/graduation.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/instinct-cleanup.d.ts +57 -0
- package/dist/instinct-cleanup.d.ts.map +1 -0
- package/dist/instinct-cleanup.js +150 -0
- package/dist/instinct-cleanup.js.map +1 -0
- package/dist/instinct-graduate.d.ts +43 -0
- package/dist/instinct-graduate.d.ts.map +1 -0
- package/dist/instinct-graduate.js +253 -0
- package/dist/instinct-graduate.js.map +1 -0
- package/dist/instinct-parser.d.ts.map +1 -1
- package/dist/instinct-parser.js +12 -0
- package/dist/instinct-parser.js.map +1 -1
- package/dist/instinct-tools.d.ts.map +1 -1
- package/dist/instinct-tools.js +19 -0
- package/dist/instinct-tools.js.map +1 -1
- package/dist/instinct-validator.d.ts +61 -0
- package/dist/instinct-validator.d.ts.map +1 -0
- package/dist/instinct-validator.js +235 -0
- package/dist/instinct-validator.js.map +1 -0
- package/dist/prompts/analyzer-system-single-shot.d.ts.map +1 -1
- package/dist/prompts/analyzer-system-single-shot.js +41 -1
- package/dist/prompts/analyzer-system-single-shot.js.map +1 -1
- package/dist/prompts/analyzer-user-single-shot.d.ts.map +1 -1
- package/dist/prompts/analyzer-user-single-shot.js +1 -1
- package/dist/prompts/analyzer-user-single-shot.js.map +1 -1
- package/dist/skill-scaffold.d.ts +23 -0
- package/dist/skill-scaffold.d.ts.map +1 -0
- package/dist/skill-scaffold.js +62 -0
- package/dist/skill-scaffold.js.map +1 -0
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/agents-md.ts +73 -3
- package/src/cli/analyze-single-shot.ts +33 -3
- package/src/cli/analyze.ts +19 -8
- package/src/command-scaffold.ts +105 -0
- package/src/confidence.ts +2 -1
- package/src/config.ts +40 -0
- package/src/graduation.ts +243 -0
- package/src/index.ts +14 -0
- package/src/instinct-cleanup.ts +204 -0
- package/src/instinct-graduate.ts +377 -0
- package/src/instinct-parser.ts +12 -0
- package/src/instinct-tools.ts +26 -0
- package/src/instinct-validator.ts +287 -0
- package/src/prompts/analyzer-system-single-shot.ts +41 -1
- package/src/prompts/analyzer-user-single-shot.ts +6 -0
- package/src/skill-scaffold.ts +90 -0
- package/src/types.ts +10 -0
package/src/cli/analyze.ts
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
} from "../storage.js";
|
|
23
23
|
import { countObservations } from "../observations.js";
|
|
24
24
|
import { runDecayPass } from "../instinct-decay.js";
|
|
25
|
+
import { runCleanupPass } from "../instinct-cleanup.js";
|
|
25
26
|
import { tailObservationsSince } from "../prompts/analyzer-user.js";
|
|
26
27
|
import { buildSingleShotSystemPrompt } from "../prompts/analyzer-system-single-shot.js";
|
|
27
28
|
import { buildSingleShotUserPrompt } from "../prompts/analyzer-user-single-shot.js";
|
|
@@ -186,6 +187,7 @@ async function analyzeProject(
|
|
|
186
187
|
const startTime = Date.now();
|
|
187
188
|
logger.projectStart(project.id, project.name, rawLineCount, obsCount);
|
|
188
189
|
|
|
190
|
+
runCleanupPass(project.id, config, baseDir);
|
|
189
191
|
runDecayPass(project.id, baseDir);
|
|
190
192
|
|
|
191
193
|
// Load current instincts inline - no tool calls needed
|
|
@@ -243,6 +245,10 @@ async function analyzeProject(
|
|
|
243
245
|
const result = await runSingleShot(context, model, apiKey, abortController.signal);
|
|
244
246
|
singleShotMessage = result.message;
|
|
245
247
|
|
|
248
|
+
// Enforce creation rate limit: only the first N create actions per run are applied.
|
|
249
|
+
const maxNewInstincts = config.max_new_instincts_per_run ?? DEFAULT_CONFIG.max_new_instincts_per_run;
|
|
250
|
+
let createsRemaining = maxNewInstincts;
|
|
251
|
+
|
|
246
252
|
for (const change of result.changes) {
|
|
247
253
|
if (change.action === "delete") {
|
|
248
254
|
const id = change.id;
|
|
@@ -253,20 +259,25 @@ async function analyzeProject(
|
|
|
253
259
|
unlinkSync(filePath);
|
|
254
260
|
instinctCounts.deleted++;
|
|
255
261
|
}
|
|
256
|
-
} else {
|
|
257
|
-
//
|
|
262
|
+
} else if (change.action === "create") {
|
|
263
|
+
if (createsRemaining <= 0) continue; // rate limit reached
|
|
258
264
|
const existing = allInstincts.find((i) => i.id === change.instinct?.id) ?? null;
|
|
259
|
-
const instinct = buildInstinctFromChange(change, existing, project.id);
|
|
265
|
+
const instinct = buildInstinctFromChange(change, existing, project.id, allInstincts);
|
|
260
266
|
if (!instinct) continue;
|
|
261
267
|
|
|
262
268
|
const dir = instinct.scope === "global" ? globalInstinctsDir : projectInstinctsDir;
|
|
263
269
|
saveInstinct(instinct, dir);
|
|
270
|
+
instinctCounts.created++;
|
|
271
|
+
createsRemaining--;
|
|
272
|
+
} else {
|
|
273
|
+
// update
|
|
274
|
+
const existing = allInstincts.find((i) => i.id === change.instinct?.id) ?? null;
|
|
275
|
+
const instinct = buildInstinctFromChange(change, existing, project.id, allInstincts);
|
|
276
|
+
if (!instinct) continue;
|
|
264
277
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
instinctCounts.updated++;
|
|
269
|
-
}
|
|
278
|
+
const dir = instinct.scope === "global" ? globalInstinctsDir : projectInstinctsDir;
|
|
279
|
+
saveInstinct(instinct, dir);
|
|
280
|
+
instinctCounts.updated++;
|
|
270
281
|
}
|
|
271
282
|
}
|
|
272
283
|
} finally {
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command scaffolding from instinct clusters.
|
|
3
|
+
*
|
|
4
|
+
* When 3+ related instincts in the same domain form an actionable workflow,
|
|
5
|
+
* generates a Pi slash command scaffold that codifies the pattern.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Instinct } from "./types.js";
|
|
9
|
+
import type { DomainCluster } from "./graduation.js";
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Types
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
export interface CommandScaffold {
|
|
16
|
+
name: string;
|
|
17
|
+
description: string;
|
|
18
|
+
domain: string;
|
|
19
|
+
content: string;
|
|
20
|
+
sourceInstinctIds: string[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Helpers
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
function toCommandName(domain: string): string {
|
|
28
|
+
return domain.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function formatInstinctAsStep(instinct: Instinct, index: number): string {
|
|
32
|
+
return [
|
|
33
|
+
`${index + 1}. **${instinct.title}**`,
|
|
34
|
+
` - Trigger: ${instinct.trigger}`,
|
|
35
|
+
` - Action: ${instinct.action}`,
|
|
36
|
+
"",
|
|
37
|
+
].join("\n");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Public API
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Generates a command scaffold from a domain cluster of instincts.
|
|
46
|
+
* The scaffold describes a slash command that encodes the workflow
|
|
47
|
+
* distilled from the instinct cluster.
|
|
48
|
+
*/
|
|
49
|
+
export function generateCommandScaffold(cluster: DomainCluster): CommandScaffold {
|
|
50
|
+
const name = toCommandName(cluster.domain);
|
|
51
|
+
const sortedInstincts = [...cluster.instincts].sort(
|
|
52
|
+
(a, b) => b.confidence - a.confidence
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const description = `Learned ${cluster.domain} workflow from coding sessions. ` +
|
|
56
|
+
`Encodes ${sortedInstincts.length} steps distilled from instinct observations.`;
|
|
57
|
+
|
|
58
|
+
const steps = sortedInstincts.map((inst, i) => formatInstinctAsStep(inst, i));
|
|
59
|
+
|
|
60
|
+
const content = [
|
|
61
|
+
`# /${name} Command`,
|
|
62
|
+
"",
|
|
63
|
+
`> Auto-generated from ${sortedInstincts.length} graduated instincts in the "${cluster.domain}" domain.`,
|
|
64
|
+
"",
|
|
65
|
+
`## Description`,
|
|
66
|
+
"",
|
|
67
|
+
description,
|
|
68
|
+
"",
|
|
69
|
+
`## Command: \`/${name}\``,
|
|
70
|
+
"",
|
|
71
|
+
`When invoked, this command should guide the agent through these steps:`,
|
|
72
|
+
"",
|
|
73
|
+
...steps,
|
|
74
|
+
`## Implementation Notes`,
|
|
75
|
+
"",
|
|
76
|
+
`Register this command in your Pi extension's \`index.ts\`:`,
|
|
77
|
+
"",
|
|
78
|
+
"```typescript",
|
|
79
|
+
`pi.registerCommand("${name}", {`,
|
|
80
|
+
` description: "${description.replace(/"/g, '\\"')}",`,
|
|
81
|
+
` handler: async (args, ctx) => {`,
|
|
82
|
+
` // TODO: Implement ${name} workflow`,
|
|
83
|
+
` },`,
|
|
84
|
+
`});`,
|
|
85
|
+
"```",
|
|
86
|
+
"",
|
|
87
|
+
].join("\n");
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
name,
|
|
91
|
+
description,
|
|
92
|
+
domain: cluster.domain,
|
|
93
|
+
content,
|
|
94
|
+
sourceInstinctIds: sortedInstincts.map((i) => i.id),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Generates command scaffolds for all qualifying clusters.
|
|
100
|
+
*/
|
|
101
|
+
export function generateAllCommandScaffolds(
|
|
102
|
+
clusters: DomainCluster[]
|
|
103
|
+
): CommandScaffold[] {
|
|
104
|
+
return clusters.map(generateCommandScaffold);
|
|
105
|
+
}
|
package/src/confidence.ts
CHANGED
|
@@ -26,7 +26,8 @@ const DELTA_CONTRADICTED = -0.15;
|
|
|
26
26
|
const DELTA_INACTIVE = 0;
|
|
27
27
|
|
|
28
28
|
// applyPassiveDecay
|
|
29
|
-
|
|
29
|
+
// Increased from 0.02 to 0.05: at 0.5 confidence, reaches 0.1 in ~8 weeks instead of 20.
|
|
30
|
+
const DECAY_PER_WEEK = 0.05;
|
|
30
31
|
const MS_PER_WEEK = 7 * 24 * 60 * 60 * 1000;
|
|
31
32
|
|
|
32
33
|
// ---------------------------------------------------------------------------
|
package/src/config.ts
CHANGED
|
@@ -38,6 +38,34 @@ export const CONFIG_PATH = path.join(
|
|
|
38
38
|
"config.json"
|
|
39
39
|
);
|
|
40
40
|
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Graduation maturity criteria
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
/** Minimum age in days before an instinct is eligible for graduation. */
|
|
46
|
+
export const GRADUATION_MIN_AGE_DAYS = 7;
|
|
47
|
+
|
|
48
|
+
/** Minimum confidence to qualify for graduation. */
|
|
49
|
+
export const GRADUATION_MIN_CONFIDENCE = 0.75;
|
|
50
|
+
|
|
51
|
+
/** Minimum confirmed_count to qualify for graduation. */
|
|
52
|
+
export const GRADUATION_MIN_CONFIRMED = 3;
|
|
53
|
+
|
|
54
|
+
/** Maximum contradicted_count allowed for graduation. */
|
|
55
|
+
export const GRADUATION_MAX_CONTRADICTED = 1;
|
|
56
|
+
|
|
57
|
+
/** Minimum related instincts in same domain to propose a skill scaffold. */
|
|
58
|
+
export const GRADUATION_SKILL_CLUSTER_SIZE = 3;
|
|
59
|
+
|
|
60
|
+
/** Minimum related instincts in same domain to propose a command scaffold. */
|
|
61
|
+
export const GRADUATION_COMMAND_CLUSTER_SIZE = 3;
|
|
62
|
+
|
|
63
|
+
/** Maximum instinct age in days before TTL cull (aggressive decay / deletion). */
|
|
64
|
+
export const GRADUATION_TTL_MAX_DAYS = 28;
|
|
65
|
+
|
|
66
|
+
/** Confidence threshold below which TTL-expired instincts are deleted outright. */
|
|
67
|
+
export const GRADUATION_TTL_CULL_CONFIDENCE = 0.3;
|
|
68
|
+
|
|
41
69
|
export const DEFAULT_CONFIG: Config = {
|
|
42
70
|
run_interval_minutes: 5,
|
|
43
71
|
min_observations_to_analyze: 20,
|
|
@@ -49,6 +77,12 @@ export const DEFAULT_CONFIG: Config = {
|
|
|
49
77
|
active_hours_start: 8,
|
|
50
78
|
active_hours_end: 23,
|
|
51
79
|
max_idle_seconds: 1800,
|
|
80
|
+
// Volume control defaults
|
|
81
|
+
max_total_instincts_per_project: 30,
|
|
82
|
+
max_total_instincts_global: 20,
|
|
83
|
+
max_new_instincts_per_run: 3,
|
|
84
|
+
flagged_cleanup_days: 7,
|
|
85
|
+
instinct_ttl_days: 28,
|
|
52
86
|
};
|
|
53
87
|
|
|
54
88
|
// ---------------------------------------------------------------------------
|
|
@@ -68,6 +102,12 @@ const PartialConfigSchema = Type.Partial(
|
|
|
68
102
|
active_hours_end: Type.Number(),
|
|
69
103
|
max_idle_seconds: Type.Number(),
|
|
70
104
|
log_path: Type.String(),
|
|
105
|
+
// Volume control
|
|
106
|
+
max_total_instincts_per_project: Type.Number(),
|
|
107
|
+
max_total_instincts_global: Type.Number(),
|
|
108
|
+
max_new_instincts_per_run: Type.Number(),
|
|
109
|
+
flagged_cleanup_days: Type.Number(),
|
|
110
|
+
instinct_ttl_days: Type.Number(),
|
|
71
111
|
})
|
|
72
112
|
);
|
|
73
113
|
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graduation pipeline - pure functions for instinct lifecycle management.
|
|
3
|
+
*
|
|
4
|
+
* Determines which instincts are mature enough to graduate into permanent
|
|
5
|
+
* knowledge (AGENTS.md, skills, or commands), and which have exceeded
|
|
6
|
+
* their TTL and should be culled.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Instinct, GraduationTarget } from "./types.js";
|
|
10
|
+
import {
|
|
11
|
+
GRADUATION_MIN_AGE_DAYS,
|
|
12
|
+
GRADUATION_MIN_CONFIDENCE,
|
|
13
|
+
GRADUATION_MIN_CONFIRMED,
|
|
14
|
+
GRADUATION_MAX_CONTRADICTED,
|
|
15
|
+
GRADUATION_SKILL_CLUSTER_SIZE,
|
|
16
|
+
GRADUATION_COMMAND_CLUSTER_SIZE,
|
|
17
|
+
GRADUATION_TTL_MAX_DAYS,
|
|
18
|
+
GRADUATION_TTL_CULL_CONFIDENCE,
|
|
19
|
+
} from "./config.js";
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Constants
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Types
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
export interface MaturityCheck {
|
|
32
|
+
eligible: boolean;
|
|
33
|
+
reasons: string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface GraduationCandidate {
|
|
37
|
+
instinct: Instinct;
|
|
38
|
+
target: GraduationTarget;
|
|
39
|
+
reason: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface DomainCluster {
|
|
43
|
+
domain: string;
|
|
44
|
+
instincts: Instinct[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface TtlResult {
|
|
48
|
+
toCull: Instinct[];
|
|
49
|
+
toDecay: Instinct[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Age helpers
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Returns the age of an instinct in days based on created_at.
|
|
58
|
+
* Uses a reference date for testability.
|
|
59
|
+
*/
|
|
60
|
+
export function getAgeDays(instinct: Instinct, now = Date.now()): number {
|
|
61
|
+
const createdAt = new Date(instinct.created_at).getTime();
|
|
62
|
+
return Math.max(0, (now - createdAt) / MS_PER_DAY);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Maturity check
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Checks whether an instinct meets all graduation maturity criteria.
|
|
71
|
+
* Returns structured result with reasons for any failures.
|
|
72
|
+
*/
|
|
73
|
+
export function checkMaturity(
|
|
74
|
+
instinct: Instinct,
|
|
75
|
+
agentsMdContent: string | null,
|
|
76
|
+
now = Date.now()
|
|
77
|
+
): MaturityCheck {
|
|
78
|
+
const reasons: string[] = [];
|
|
79
|
+
|
|
80
|
+
if (instinct.graduated_to !== undefined) {
|
|
81
|
+
return { eligible: false, reasons: [`Already graduated to ${instinct.graduated_to}`] };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (instinct.flagged_for_removal) {
|
|
85
|
+
return { eligible: false, reasons: ["Flagged for removal"] };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const ageDays = getAgeDays(instinct, now);
|
|
89
|
+
if (ageDays < GRADUATION_MIN_AGE_DAYS) {
|
|
90
|
+
reasons.push(
|
|
91
|
+
`Age ${ageDays.toFixed(1)}d < ${GRADUATION_MIN_AGE_DAYS}d minimum`
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (instinct.confidence < GRADUATION_MIN_CONFIDENCE) {
|
|
96
|
+
reasons.push(
|
|
97
|
+
`Confidence ${instinct.confidence.toFixed(2)} < ${GRADUATION_MIN_CONFIDENCE} minimum`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (instinct.confirmed_count < GRADUATION_MIN_CONFIRMED) {
|
|
102
|
+
reasons.push(
|
|
103
|
+
`Confirmed ${instinct.confirmed_count} < ${GRADUATION_MIN_CONFIRMED} minimum`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (instinct.contradicted_count > GRADUATION_MAX_CONTRADICTED) {
|
|
108
|
+
reasons.push(
|
|
109
|
+
`Contradicted ${instinct.contradicted_count} > ${GRADUATION_MAX_CONTRADICTED} maximum`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Check for duplicates in AGENTS.md (simple substring match on title/trigger)
|
|
114
|
+
if (agentsMdContent !== null) {
|
|
115
|
+
const lowerContent = agentsMdContent.toLowerCase();
|
|
116
|
+
const titleMatch = lowerContent.includes(instinct.title.toLowerCase());
|
|
117
|
+
const triggerMatch = lowerContent.includes(instinct.trigger.toLowerCase());
|
|
118
|
+
if (titleMatch && triggerMatch) {
|
|
119
|
+
reasons.push("Appears to duplicate existing AGENTS.md content");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return { eligible: reasons.length === 0, reasons };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
// Candidate scanning
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Finds all instincts that qualify for graduation to AGENTS.md.
|
|
132
|
+
*/
|
|
133
|
+
export function findAgentsMdCandidates(
|
|
134
|
+
instincts: Instinct[],
|
|
135
|
+
agentsMdContent: string | null,
|
|
136
|
+
now = Date.now()
|
|
137
|
+
): GraduationCandidate[] {
|
|
138
|
+
const candidates: GraduationCandidate[] = [];
|
|
139
|
+
|
|
140
|
+
for (const instinct of instincts) {
|
|
141
|
+
const check = checkMaturity(instinct, agentsMdContent, now);
|
|
142
|
+
if (check.eligible) {
|
|
143
|
+
candidates.push({
|
|
144
|
+
instinct,
|
|
145
|
+
target: "agents-md",
|
|
146
|
+
reason: `Mature instinct (${instinct.confidence.toFixed(2)} confidence, ${instinct.confirmed_count} confirmations)`,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return candidates;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Groups instincts by domain, returning only clusters meeting the size threshold.
|
|
156
|
+
*/
|
|
157
|
+
export function findDomainClusters(
|
|
158
|
+
instincts: Instinct[],
|
|
159
|
+
minSize: number
|
|
160
|
+
): DomainCluster[] {
|
|
161
|
+
const byDomain = new Map<string, Instinct[]>();
|
|
162
|
+
|
|
163
|
+
for (const instinct of instincts) {
|
|
164
|
+
if (instinct.graduated_to !== undefined) continue;
|
|
165
|
+
if (instinct.flagged_for_removal) continue;
|
|
166
|
+
|
|
167
|
+
const existing = byDomain.get(instinct.domain) ?? [];
|
|
168
|
+
byDomain.set(instinct.domain, [...existing, instinct]);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const clusters: DomainCluster[] = [];
|
|
172
|
+
for (const [domain, domainInstincts] of byDomain) {
|
|
173
|
+
if (domainInstincts.length >= minSize) {
|
|
174
|
+
clusters.push({ domain, instincts: domainInstincts });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return clusters.sort((a, b) => b.instincts.length - a.instincts.length);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Finds instinct clusters that qualify for skill scaffolding.
|
|
183
|
+
*/
|
|
184
|
+
export function findSkillCandidates(instincts: Instinct[]): DomainCluster[] {
|
|
185
|
+
return findDomainClusters(instincts, GRADUATION_SKILL_CLUSTER_SIZE);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Finds instinct clusters that qualify for command scaffolding.
|
|
190
|
+
*/
|
|
191
|
+
export function findCommandCandidates(instincts: Instinct[]): DomainCluster[] {
|
|
192
|
+
return findDomainClusters(instincts, GRADUATION_COMMAND_CLUSTER_SIZE);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
// TTL enforcement
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Identifies instincts that have exceeded the TTL without graduating.
|
|
201
|
+
* - Instincts with confidence < cull threshold are marked for outright deletion
|
|
202
|
+
* - Others are marked for aggressive decay
|
|
203
|
+
*/
|
|
204
|
+
export function enforceTtl(
|
|
205
|
+
instincts: Instinct[],
|
|
206
|
+
now = Date.now()
|
|
207
|
+
): TtlResult {
|
|
208
|
+
const toCull: Instinct[] = [];
|
|
209
|
+
const toDecay: Instinct[] = [];
|
|
210
|
+
|
|
211
|
+
for (const instinct of instincts) {
|
|
212
|
+
// Skip already-graduated instincts
|
|
213
|
+
if (instinct.graduated_to !== undefined) continue;
|
|
214
|
+
|
|
215
|
+
const ageDays = getAgeDays(instinct, now);
|
|
216
|
+
if (ageDays < GRADUATION_TTL_MAX_DAYS) continue;
|
|
217
|
+
|
|
218
|
+
if (instinct.confidence < GRADUATION_TTL_CULL_CONFIDENCE) {
|
|
219
|
+
toCull.push(instinct);
|
|
220
|
+
} else {
|
|
221
|
+
toDecay.push(instinct);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return { toCull, toDecay };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Marks an instinct as graduated. Returns a new instinct with graduated_to
|
|
230
|
+
* and graduated_at set. Does not mutate the original.
|
|
231
|
+
*/
|
|
232
|
+
export function markGraduated(
|
|
233
|
+
instinct: Instinct,
|
|
234
|
+
target: GraduationTarget,
|
|
235
|
+
now = new Date()
|
|
236
|
+
): Instinct {
|
|
237
|
+
return {
|
|
238
|
+
...instinct,
|
|
239
|
+
graduated_to: target,
|
|
240
|
+
graduated_at: now.toISOString(),
|
|
241
|
+
updated_at: now.toISOString(),
|
|
242
|
+
};
|
|
243
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -32,6 +32,7 @@ import { handleInstinctImport, COMMAND_NAME as IMPORT_CMD } from "./instinct-imp
|
|
|
32
32
|
import { handleInstinctPromote, COMMAND_NAME as PROMOTE_CMD } from "./instinct-promote.js";
|
|
33
33
|
import { handleInstinctEvolve, COMMAND_NAME as EVOLVE_CMD } from "./instinct-evolve.js";
|
|
34
34
|
import { handleInstinctProjects, COMMAND_NAME as PROJECTS_CMD } from "./instinct-projects.js";
|
|
35
|
+
import { handleInstinctGraduate, COMMAND_NAME as GRADUATE_CMD } from "./instinct-graduate.js";
|
|
35
36
|
import { registerAllTools } from "./instinct-tools.js";
|
|
36
37
|
import { logError } from "./error-logger.js";
|
|
37
38
|
import type { Config, InstalledSkill, ProjectEntry } from "./types.js";
|
|
@@ -193,4 +194,17 @@ export default function (pi: ExtensionAPI): void {
|
|
|
193
194
|
handler: (args: string, ctx: ExtensionCommandContext) =>
|
|
194
195
|
handleInstinctProjects(args, ctx),
|
|
195
196
|
});
|
|
197
|
+
|
|
198
|
+
pi.registerCommand(GRADUATE_CMD, {
|
|
199
|
+
description: "Graduate mature instincts to AGENTS.md, skills, or commands",
|
|
200
|
+
handler: (args: string, ctx: ExtensionCommandContext) =>
|
|
201
|
+
handleInstinctGraduate(
|
|
202
|
+
args,
|
|
203
|
+
ctx,
|
|
204
|
+
pi,
|
|
205
|
+
project?.id,
|
|
206
|
+
undefined,
|
|
207
|
+
project?.root ?? null
|
|
208
|
+
),
|
|
209
|
+
});
|
|
196
210
|
}
|