@planu/cli 0.81.1 → 0.83.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/config/workers-registry.json +13 -0
- package/dist/engine/bdd-formatter.d.ts +22 -0
- package/dist/engine/bdd-formatter.d.ts.map +1 -0
- package/dist/engine/bdd-formatter.js +182 -0
- package/dist/engine/bdd-formatter.js.map +1 -0
- package/dist/engine/validator/dor-dod.d.ts.map +1 -1
- package/dist/engine/validator/dor-dod.js +33 -0
- package/dist/engine/validator/dor-dod.js.map +1 -1
- package/dist/engine/worker-config-loader.d.ts +48 -0
- package/dist/engine/worker-config-loader.d.ts.map +1 -0
- package/dist/engine/worker-config-loader.js +167 -0
- package/dist/engine/worker-config-loader.js.map +1 -0
- package/dist/engine/workers/handlers/auto-drift.d.ts +13 -0
- package/dist/engine/workers/handlers/auto-drift.d.ts.map +1 -0
- package/dist/engine/workers/handlers/auto-drift.js +178 -0
- package/dist/engine/workers/handlers/auto-drift.js.map +1 -0
- package/dist/engine/workers/index.d.ts +4 -0
- package/dist/engine/workers/index.d.ts.map +1 -1
- package/dist/engine/workers/index.js +3 -0
- package/dist/engine/workers/index.js.map +1 -1
- package/dist/engine/workers/worker-crash-recovery.d.ts +30 -0
- package/dist/engine/workers/worker-crash-recovery.d.ts.map +1 -0
- package/dist/engine/workers/worker-crash-recovery.js +115 -0
- package/dist/engine/workers/worker-crash-recovery.js.map +1 -0
- package/dist/engine/workers/worker-engine.d.ts +1 -1
- package/dist/engine/workers/worker-engine.d.ts.map +1 -1
- package/dist/engine/workers/worker-engine.js +13 -1
- package/dist/engine/workers/worker-engine.js.map +1 -1
- package/dist/engine/workers/worker-heartbeat.d.ts +34 -0
- package/dist/engine/workers/worker-heartbeat.d.ts.map +1 -0
- package/dist/engine/workers/worker-heartbeat.js +84 -0
- package/dist/engine/workers/worker-heartbeat.js.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/storage/drift-cache-store.d.ts +40 -0
- package/dist/storage/drift-cache-store.d.ts.map +1 -0
- package/dist/storage/drift-cache-store.js +103 -0
- package/dist/storage/drift-cache-store.js.map +1 -0
- package/dist/storage/worker-metrics-store.d.ts +20 -0
- package/dist/storage/worker-metrics-store.d.ts.map +1 -0
- package/dist/storage/worker-metrics-store.js +103 -0
- package/dist/storage/worker-metrics-store.js.map +1 -0
- package/dist/tools/create-spec-hu/hu-body-generators.d.ts +2 -1
- package/dist/tools/create-spec-hu/hu-body-generators.d.ts.map +1 -1
- package/dist/tools/create-spec-hu/hu-body-generators.js +16 -8
- package/dist/tools/create-spec-hu/hu-body-generators.js.map +1 -1
- package/dist/tools/create-spec-hu.d.ts +2 -1
- package/dist/tools/create-spec-hu.d.ts.map +1 -1
- package/dist/tools/create-spec-hu.js +3 -2
- package/dist/tools/create-spec-hu.js.map +1 -1
- package/dist/tools/create-spec.js +2 -2
- package/dist/tools/create-spec.js.map +1 -1
- package/dist/tools/git/github-issues-ops.d.ts +29 -0
- package/dist/tools/git/github-issues-ops.d.ts.map +1 -0
- package/dist/tools/git/github-issues-ops.js +349 -0
- package/dist/tools/git/github-issues-ops.js.map +1 -0
- package/dist/tools/manage-git.d.ts.map +1 -1
- package/dist/tools/manage-git.js +20 -0
- package/dist/tools/manage-git.js.map +1 -1
- package/dist/tools/register-spec-tools/core-spec-tools.d.ts.map +1 -1
- package/dist/tools/register-spec-tools/core-spec-tools.js +6 -0
- package/dist/tools/register-spec-tools/core-spec-tools.js.map +1 -1
- package/dist/tools/safe-handler.d.ts.map +1 -1
- package/dist/tools/safe-handler.js +73 -2
- package/dist/tools/safe-handler.js.map +1 -1
- package/dist/tools/schemas/github.d.ts +1 -1
- package/dist/tools/schemas/lifecycle.d.ts +3 -0
- package/dist/tools/schemas/lifecycle.d.ts.map +1 -1
- package/dist/tools/schemas/lifecycle.js +4 -1
- package/dist/tools/schemas/lifecycle.js.map +1 -1
- package/dist/tools/update-status.d.ts.map +1 -1
- package/dist/tools/update-status.js +4 -2
- package/dist/tools/update-status.js.map +1 -1
- package/dist/types/common/tech-enums.d.ts +1 -1
- package/dist/types/common/tech-enums.d.ts.map +1 -1
- package/dist/types/git.d.ts +48 -0
- package/dist/types/git.d.ts.map +1 -1
- package/dist/types/spec/inputs.d.ts +2 -0
- package/dist/types/spec/inputs.d.ts.map +1 -1
- package/dist/types/workers.d.ts +36 -0
- package/dist/types/workers.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/config/workers-registry.json +13 -0
- package/src/i18n/messages/en.json +14 -0
- package/src/i18n/messages/es.json +14 -0
- package/src/i18n/messages/pt.json +14 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// engine/workers/handlers/auto-drift.ts — Auto drift detection on file changes (SPEC-225)
|
|
2
|
+
import { relative, extname } from 'node:path';
|
|
3
|
+
import { readdir, stat } from 'node:fs/promises';
|
|
4
|
+
import { specStore } from '../../../storage/index.js';
|
|
5
|
+
import { hashProjectPath } from '../../../storage/base-store.js';
|
|
6
|
+
import { DriftCacheStore } from '../../../storage/drift-cache-store.js';
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Constants
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
const WORKER_NAME = 'auto-drift';
|
|
11
|
+
/** Default drift threshold: flag specs with score >= 0.3 */
|
|
12
|
+
const DEFAULT_DRIFT_THRESHOLD = 0.3;
|
|
13
|
+
/** Source file extensions tracked for heuristic scoring */
|
|
14
|
+
const SOURCE_EXTENSIONS = new Set([
|
|
15
|
+
'.ts',
|
|
16
|
+
'.js',
|
|
17
|
+
'.tsx',
|
|
18
|
+
'.jsx',
|
|
19
|
+
'.py',
|
|
20
|
+
'.go',
|
|
21
|
+
'.rs',
|
|
22
|
+
'.java',
|
|
23
|
+
'.kt',
|
|
24
|
+
]);
|
|
25
|
+
/** Directories to skip when counting total source files */
|
|
26
|
+
const IGNORE_DIRS = new Set([
|
|
27
|
+
'node_modules',
|
|
28
|
+
'.git',
|
|
29
|
+
'dist',
|
|
30
|
+
'build',
|
|
31
|
+
'__pycache__',
|
|
32
|
+
'target',
|
|
33
|
+
'.venv',
|
|
34
|
+
'vendor',
|
|
35
|
+
'.next',
|
|
36
|
+
'.nuxt',
|
|
37
|
+
'coverage',
|
|
38
|
+
'.turbo',
|
|
39
|
+
'.cache',
|
|
40
|
+
'tests',
|
|
41
|
+
'__tests__',
|
|
42
|
+
]);
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// Heuristic drift scorer
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
/**
|
|
47
|
+
* Lightweight drift heuristic: ratio of changedFiles that are source files
|
|
48
|
+
* relative to an estimated total source file count for the project.
|
|
49
|
+
*
|
|
50
|
+
* Returns a score in [0, 1] where 1 means maximum estimated drift.
|
|
51
|
+
*/
|
|
52
|
+
async function computeHeuristicDriftScore(projectDir, changedFiles) {
|
|
53
|
+
const relevantChanged = changedFiles.filter((f) => SOURCE_EXTENSIONS.has(extname(f)));
|
|
54
|
+
if (relevantChanged.length === 0) {
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
const totalFiles = await countSourceFiles(projectDir);
|
|
58
|
+
if (totalFiles === 0) {
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
// Score = changed source files / total source files, capped at 1
|
|
62
|
+
return Math.min(relevantChanged.length / totalFiles, 1);
|
|
63
|
+
}
|
|
64
|
+
async function countSourceFiles(dir) {
|
|
65
|
+
let count = 0;
|
|
66
|
+
let entries;
|
|
67
|
+
try {
|
|
68
|
+
entries = await readdir(dir);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
for (const entry of entries) {
|
|
74
|
+
if (IGNORE_DIRS.has(entry)) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const fullPath = `${dir}/${entry}`;
|
|
78
|
+
let st;
|
|
79
|
+
try {
|
|
80
|
+
st = await stat(fullPath);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (st.isDirectory()) {
|
|
86
|
+
count += await countSourceFiles(fullPath);
|
|
87
|
+
}
|
|
88
|
+
else if (st.isFile() && SOURCE_EXTENSIONS.has(extname(entry))) {
|
|
89
|
+
count++;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return count;
|
|
93
|
+
}
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// Public handler
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
/**
|
|
98
|
+
* Worker handler for auto-drift detection.
|
|
99
|
+
*
|
|
100
|
+
* Triggered on source file changes. For each spec in 'implementing' or 'done'
|
|
101
|
+
* state, computes a lightweight drift score. If score >= PLANU_DRIFT_THRESHOLD,
|
|
102
|
+
* persists the result to DriftCacheStore with shown=false so the next tool call
|
|
103
|
+
* can display a banner warning.
|
|
104
|
+
*
|
|
105
|
+
* Respects PLANU_AUTO_DRIFT=false to disable entirely.
|
|
106
|
+
*/
|
|
107
|
+
export async function runAutoDrift(projectDir, changedFiles) {
|
|
108
|
+
const startedAt = new Date().toISOString();
|
|
109
|
+
const start = Date.now();
|
|
110
|
+
if (process.env.PLANU_AUTO_DRIFT === 'false') {
|
|
111
|
+
return makeResult(WORKER_NAME, startedAt, start, 'skipped', [], 0);
|
|
112
|
+
}
|
|
113
|
+
const effectiveChanged = changedFiles ?? [];
|
|
114
|
+
const projectId = hashProjectPath(projectDir);
|
|
115
|
+
let specs;
|
|
116
|
+
try {
|
|
117
|
+
specs = await specStore.listSpecs(projectId);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return makeResult(WORKER_NAME, startedAt, start, 'skipped', [], 0);
|
|
121
|
+
}
|
|
122
|
+
const activeSpecs = specs.filter((s) => s.status === 'implementing' || s.status === 'done');
|
|
123
|
+
if (activeSpecs.length === 0) {
|
|
124
|
+
return makeResult(WORKER_NAME, startedAt, start, 'success', [], effectiveChanged.length);
|
|
125
|
+
}
|
|
126
|
+
const findings = await buildDriftFindings(projectDir, effectiveChanged, activeSpecs, projectId);
|
|
127
|
+
return makeResult(WORKER_NAME, startedAt, start, 'success', findings, effectiveChanged.length);
|
|
128
|
+
}
|
|
129
|
+
function makeResult(workerName, startedAt, start, status, findings, analyzedFiles) {
|
|
130
|
+
return {
|
|
131
|
+
workerName,
|
|
132
|
+
startedAt,
|
|
133
|
+
completedAt: new Date().toISOString(),
|
|
134
|
+
durationMs: Date.now() - start,
|
|
135
|
+
status,
|
|
136
|
+
findings,
|
|
137
|
+
analyzedFiles,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
async function buildDriftFindings(projectDir, effectiveChanged, activeSpecs, projectId) {
|
|
141
|
+
const threshold = parseDriftThreshold();
|
|
142
|
+
const cacheStore = new DriftCacheStore(projectId);
|
|
143
|
+
const driftScore = await computeHeuristicDriftScore(projectDir, effectiveChanged);
|
|
144
|
+
const driftedFiles = effectiveChanged
|
|
145
|
+
.filter((f) => SOURCE_EXTENSIONS.has(extname(f)))
|
|
146
|
+
.map((f) => relative(projectDir, f));
|
|
147
|
+
const findings = [];
|
|
148
|
+
for (const spec of activeSpecs) {
|
|
149
|
+
if (driftScore >= threshold) {
|
|
150
|
+
await cacheStore.upsert({
|
|
151
|
+
specId: spec.id,
|
|
152
|
+
driftScore,
|
|
153
|
+
driftedFiles,
|
|
154
|
+
cachedAt: new Date().toISOString(),
|
|
155
|
+
});
|
|
156
|
+
findings.push({
|
|
157
|
+
severity: driftScore >= 0.7 ? 'high' : 'warning',
|
|
158
|
+
file: '',
|
|
159
|
+
message: `Spec ${spec.id} may have drifted (score: ${driftScore.toFixed(2)}, threshold: ${threshold.toFixed(2)})`,
|
|
160
|
+
suggestion: `Run detect_drift for spec ${spec.id} to see full drift report`,
|
|
161
|
+
category: 'auto-drift',
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return findings;
|
|
166
|
+
}
|
|
167
|
+
// ---------------------------------------------------------------------------
|
|
168
|
+
// Helpers
|
|
169
|
+
// ---------------------------------------------------------------------------
|
|
170
|
+
function parseDriftThreshold() {
|
|
171
|
+
const env = process.env.PLANU_DRIFT_THRESHOLD;
|
|
172
|
+
if (env === undefined || env === '') {
|
|
173
|
+
return DEFAULT_DRIFT_THRESHOLD;
|
|
174
|
+
}
|
|
175
|
+
const parsed = parseFloat(env);
|
|
176
|
+
return Number.isFinite(parsed) && parsed > 0 && parsed <= 1 ? parsed : DEFAULT_DRIFT_THRESHOLD;
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=auto-drift.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auto-drift.js","sourceRoot":"","sources":["../../../../src/engine/workers/handlers/auto-drift.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,4 +5,8 @@ export { runOptimization } from './handlers/optimization.js';
|
|
|
5
5
|
export { runCodeMap } from './handlers/code-map.js';
|
|
6
6
|
export { runDocumentation } from './handlers/documentation.js';
|
|
7
7
|
export { runDependencyHealth } from './handlers/dependency-health.js';
|
|
8
|
+
export { runAutoDrift } from './handlers/auto-drift.js';
|
|
9
|
+
export { WorkerHeartbeat } from './worker-heartbeat.js';
|
|
10
|
+
export { WorkerCrashRecovery } from './worker-crash-recovery.js';
|
|
11
|
+
export type { WorkerCrashMetrics } from './worker-crash-recovery.js';
|
|
8
12
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/engine/workers/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,YAAY,EACZ,eAAe,EACf,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/engine/workers/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,YAAY,EACZ,eAAe,EACf,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,YAAY,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC"}
|
|
@@ -6,4 +6,7 @@ export { runOptimization } from './handlers/optimization.js';
|
|
|
6
6
|
export { runCodeMap } from './handlers/code-map.js';
|
|
7
7
|
export { runDocumentation } from './handlers/documentation.js';
|
|
8
8
|
export { runDependencyHealth } from './handlers/dependency-health.js';
|
|
9
|
+
export { runAutoDrift } from './handlers/auto-drift.js';
|
|
10
|
+
export { WorkerHeartbeat } from './worker-heartbeat.js';
|
|
11
|
+
export { WorkerCrashRecovery } from './worker-crash-recovery.js';
|
|
9
12
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/engine/workers/index.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,OAAO,EACL,YAAY,EACZ,eAAe,EACf,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/engine/workers/index.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,OAAO,EACL,YAAY,EACZ,eAAe,EACf,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { WorkerCrashMetrics } from '../../types/index.js';
|
|
2
|
+
import type { WorkerEngine } from './worker-engine.js';
|
|
3
|
+
import type { WorkerHeartbeat } from './worker-heartbeat.js';
|
|
4
|
+
export type { WorkerCrashMetrics };
|
|
5
|
+
/**
|
|
6
|
+
* Listens for 'worker-dead' events from WorkerHeartbeat instances and
|
|
7
|
+
* auto-restarts the dead worker via WorkerEngine with exponential backoff.
|
|
8
|
+
* Stops restarting after maxRestarts consecutive crashes.
|
|
9
|
+
*/
|
|
10
|
+
export declare class WorkerCrashRecovery {
|
|
11
|
+
private readonly engine;
|
|
12
|
+
private readonly maxRestarts;
|
|
13
|
+
private readonly metricsMap;
|
|
14
|
+
private readonly timers;
|
|
15
|
+
constructor(engine: WorkerEngine, options?: {
|
|
16
|
+
maxRestarts?: number;
|
|
17
|
+
});
|
|
18
|
+
/**
|
|
19
|
+
* Register a worker and its heartbeat monitor.
|
|
20
|
+
* The recovery manager will listen for 'worker-dead' events and react.
|
|
21
|
+
*/
|
|
22
|
+
registerWorker(workerName: string, heartbeat: WorkerHeartbeat): void;
|
|
23
|
+
/** Returns current crash metrics for a given worker. */
|
|
24
|
+
getMetrics(workerName: string): WorkerCrashMetrics;
|
|
25
|
+
/** Reset crash counter for a worker (e.g. after manual intervention). */
|
|
26
|
+
resetMetrics(workerName: string): void;
|
|
27
|
+
private handleCrash;
|
|
28
|
+
private computeBackoff;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=worker-crash-recovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-crash-recovery.d.ts","sourceRoot":"","sources":["../../../src/engine/workers/worker-crash-recovery.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC/D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,YAAY,EAAE,kBAAkB,EAAE,CAAC;AAcnC;;;;GAIG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IACtC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAyC;IACpE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAoD;gBAE/D,MAAM,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE;IAKpE;;;OAGG;IACH,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,eAAe,GAAG,IAAI;IAgBpE,wDAAwD;IACxD,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,kBAAkB;IAYlD,yEAAyE;IACzE,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;YAoBxB,WAAW;IA+CzB,OAAO,CAAC,cAAc;CAIvB"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// engine/workers/worker-crash-recovery.ts — Auto-restart on crash with backoff (SPEC-137)
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// Constants
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
const DEFAULT_MAX_RESTARTS = 5;
|
|
6
|
+
const BASE_BACKOFF_MS = 1_000;
|
|
7
|
+
const MAX_BACKOFF_MS = 300_000; // 5 minutes
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// WorkerCrashRecovery
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
/**
|
|
12
|
+
* Listens for 'worker-dead' events from WorkerHeartbeat instances and
|
|
13
|
+
* auto-restarts the dead worker via WorkerEngine with exponential backoff.
|
|
14
|
+
* Stops restarting after maxRestarts consecutive crashes.
|
|
15
|
+
*/
|
|
16
|
+
export class WorkerCrashRecovery {
|
|
17
|
+
engine;
|
|
18
|
+
maxRestarts;
|
|
19
|
+
metricsMap = new Map();
|
|
20
|
+
timers = new Map();
|
|
21
|
+
constructor(engine, options) {
|
|
22
|
+
this.engine = engine;
|
|
23
|
+
this.maxRestarts = options?.maxRestarts ?? DEFAULT_MAX_RESTARTS;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Register a worker and its heartbeat monitor.
|
|
27
|
+
* The recovery manager will listen for 'worker-dead' events and react.
|
|
28
|
+
*/
|
|
29
|
+
registerWorker(workerName, heartbeat) {
|
|
30
|
+
if (!this.metricsMap.has(workerName)) {
|
|
31
|
+
this.metricsMap.set(workerName, {
|
|
32
|
+
workerName,
|
|
33
|
+
restartCount: 0,
|
|
34
|
+
lastCrashAt: null,
|
|
35
|
+
lastRestartAt: null,
|
|
36
|
+
status: 'healthy',
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
heartbeat.on('worker-dead', (deadWorkerName) => {
|
|
40
|
+
void this.handleCrash(deadWorkerName);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/** Returns current crash metrics for a given worker. */
|
|
44
|
+
getMetrics(workerName) {
|
|
45
|
+
return (this.metricsMap.get(workerName) ?? {
|
|
46
|
+
workerName,
|
|
47
|
+
restartCount: 0,
|
|
48
|
+
lastCrashAt: null,
|
|
49
|
+
lastRestartAt: null,
|
|
50
|
+
status: 'healthy',
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/** Reset crash counter for a worker (e.g. after manual intervention). */
|
|
54
|
+
resetMetrics(workerName) {
|
|
55
|
+
this.metricsMap.set(workerName, {
|
|
56
|
+
workerName,
|
|
57
|
+
restartCount: 0,
|
|
58
|
+
lastCrashAt: null,
|
|
59
|
+
lastRestartAt: null,
|
|
60
|
+
status: 'healthy',
|
|
61
|
+
});
|
|
62
|
+
const pendingTimer = this.timers.get(workerName);
|
|
63
|
+
if (pendingTimer !== undefined) {
|
|
64
|
+
clearTimeout(pendingTimer);
|
|
65
|
+
this.timers.delete(workerName);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Private
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
async handleCrash(workerName) {
|
|
72
|
+
const current = this.metricsMap.get(workerName);
|
|
73
|
+
if (!current) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (current.restartCount >= this.maxRestarts) {
|
|
77
|
+
this.metricsMap.set(workerName, {
|
|
78
|
+
...current,
|
|
79
|
+
lastCrashAt: new Date().toISOString(),
|
|
80
|
+
status: 'dead',
|
|
81
|
+
});
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const backoffMs = this.computeBackoff(current.restartCount);
|
|
85
|
+
this.metricsMap.set(workerName, {
|
|
86
|
+
...current,
|
|
87
|
+
lastCrashAt: new Date().toISOString(),
|
|
88
|
+
status: 'recovering',
|
|
89
|
+
});
|
|
90
|
+
await new Promise((resolve) => {
|
|
91
|
+
const timer = setTimeout(() => {
|
|
92
|
+
this.timers.delete(workerName);
|
|
93
|
+
resolve();
|
|
94
|
+
}, backoffMs);
|
|
95
|
+
this.timers.set(workerName, timer);
|
|
96
|
+
});
|
|
97
|
+
// Re-check in case resetMetrics was called during backoff
|
|
98
|
+
const afterBackoff = this.metricsMap.get(workerName);
|
|
99
|
+
if (!afterBackoff || afterBackoff.restartCount >= this.maxRestarts) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
this.metricsMap.set(workerName, {
|
|
103
|
+
...afterBackoff,
|
|
104
|
+
restartCount: afterBackoff.restartCount + 1,
|
|
105
|
+
lastRestartAt: new Date().toISOString(),
|
|
106
|
+
status: 'healthy',
|
|
107
|
+
});
|
|
108
|
+
this.engine.enqueue(workerName);
|
|
109
|
+
}
|
|
110
|
+
computeBackoff(restartCount) {
|
|
111
|
+
const backoff = BASE_BACKOFF_MS * Math.pow(2, restartCount);
|
|
112
|
+
return Math.min(backoff, MAX_BACKOFF_MS);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=worker-crash-recovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-crash-recovery.js","sourceRoot":"","sources":["../../../src/engine/workers/worker-crash-recovery.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,7 +7,7 @@ export declare class WorkerEngine {
|
|
|
7
7
|
private active;
|
|
8
8
|
private totalRunsCompleted;
|
|
9
9
|
private readonly overrides;
|
|
10
|
-
loadRegistry(registryPath?: string): Promise<void>;
|
|
10
|
+
loadRegistry(registryPath?: string, projectPath?: string): Promise<void>;
|
|
11
11
|
start(): void;
|
|
12
12
|
stop(): void;
|
|
13
13
|
isActive(): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker-engine.d.ts","sourceRoot":"","sources":["../../../src/engine/workers/worker-engine.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,oBAAoB,EAErB,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"worker-engine.d.ts","sourceRoot":"","sources":["../../../src/engine/workers/worker-engine.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,oBAAoB,EAErB,MAAM,sBAAsB,CAAC;AAoC9B,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAA0B;IACzC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmC;IAC5D,OAAO,CAAC,KAAK,CAAyD;IACtE,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,kBAAkB,CAAK;IAC/B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA2C;IAE/D,YAAY,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+B9E,KAAK,IAAI,IAAI;IAIb,IAAI,IAAI,IAAI;IAKZ,QAAQ,IAAI,OAAO;IAInB,oBAAoB,IAAI,gBAAgB,EAAE;IAI1C,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,oBAAoB,GAAG,IAAI;IAwBvE,OAAO,CAAC,UAAU,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO;IA4CvD,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;YA6BpD,aAAa;IAoE3B,SAAS,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,kBAAkB,GAAG,YAAY,GAAG,SAAS;IAa7E,cAAc,IAAI,MAAM;CAGzB;AAQD,wBAAgB,eAAe,IAAI,YAAY,CAG9C;AAED,wBAAgB,iBAAiB,IAAI,IAAI,CAKxC;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAY/D"}
|
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
import { readFile } from 'node:fs/promises';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { WorkerRegistrySchema } from './schema.js';
|
|
5
|
+
import { WorkerConfigLoader } from '../worker-config-loader.js';
|
|
5
6
|
import { runTestGaps } from './handlers/test-gaps.js';
|
|
6
7
|
import { runSecurityAudit } from './handlers/security-audit.js';
|
|
7
8
|
import { runOptimization } from './handlers/optimization.js';
|
|
8
9
|
import { runCodeMap } from './handlers/code-map.js';
|
|
9
10
|
import { runDocumentation } from './handlers/documentation.js';
|
|
10
11
|
import { runDependencyHealth } from './handlers/dependency-health.js';
|
|
12
|
+
import { runAutoDrift } from './handlers/auto-drift.js';
|
|
11
13
|
// ---------------------------------------------------------------------------
|
|
12
14
|
// Handler registry
|
|
13
15
|
// ---------------------------------------------------------------------------
|
|
@@ -18,6 +20,7 @@ const HANDLER_MAP = {
|
|
|
18
20
|
'handlers/code-map': runCodeMap,
|
|
19
21
|
'handlers/documentation': runDocumentation,
|
|
20
22
|
'handlers/dependency-health': runDependencyHealth,
|
|
23
|
+
'handlers/auto-drift': runAutoDrift,
|
|
21
24
|
};
|
|
22
25
|
// ---------------------------------------------------------------------------
|
|
23
26
|
// Constants
|
|
@@ -35,7 +38,7 @@ export class WorkerEngine {
|
|
|
35
38
|
active = false;
|
|
36
39
|
totalRunsCompleted = 0;
|
|
37
40
|
overrides = new Map();
|
|
38
|
-
async loadRegistry(registryPath) {
|
|
41
|
+
async loadRegistry(registryPath, projectPath) {
|
|
39
42
|
/* v8 ignore next */
|
|
40
43
|
const path = registryPath ?? join(import.meta.dirname, '../../config/workers-registry.json');
|
|
41
44
|
const raw = await readFile(path, 'utf-8');
|
|
@@ -53,6 +56,15 @@ export class WorkerEngine {
|
|
|
53
56
|
});
|
|
54
57
|
}
|
|
55
58
|
}
|
|
59
|
+
// Apply 3-layer config overrides (project file + env vars) — SPEC-138
|
|
60
|
+
if (projectPath) {
|
|
61
|
+
const configLoader = new WorkerConfigLoader(projectPath);
|
|
62
|
+
const workerNames = this.workers.map((w) => w.name);
|
|
63
|
+
const overrides = await configLoader.load(workerNames);
|
|
64
|
+
for (const [name, override] of overrides) {
|
|
65
|
+
this.applyOverride(name, override);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
56
68
|
}
|
|
57
69
|
start() {
|
|
58
70
|
this.active = true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker-engine.js","sourceRoot":"","sources":["../../../src/engine/workers/worker-engine.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAEhG,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AASjC,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;
|
|
1
|
+
{"version":3,"file":"worker-engine.js","sourceRoot":"","sources":["../../../src/engine/workers/worker-engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
/**
|
|
3
|
+
* Monitors a single worker via periodic ping checks.
|
|
4
|
+
* Emits 'worker-dead' when the worker has been silent beyond maxSilenceMs.
|
|
5
|
+
* Uses setTimeout loops (not setInterval) for clean teardown.
|
|
6
|
+
*/
|
|
7
|
+
export declare class WorkerHeartbeat extends EventEmitter {
|
|
8
|
+
private readonly workerName;
|
|
9
|
+
private readonly intervalMs;
|
|
10
|
+
private readonly maxSilenceMs;
|
|
11
|
+
private lastPing;
|
|
12
|
+
private timer;
|
|
13
|
+
private running;
|
|
14
|
+
constructor(workerName: string, options?: {
|
|
15
|
+
intervalMs?: number;
|
|
16
|
+
maxSilenceMs?: number;
|
|
17
|
+
});
|
|
18
|
+
/** Start the heartbeat monitoring loop. Idempotent. */
|
|
19
|
+
start(): void;
|
|
20
|
+
/** Stop the heartbeat loop. Safe to call multiple times. */
|
|
21
|
+
stop(): void;
|
|
22
|
+
/**
|
|
23
|
+
* Called by the worker process to signal it is alive.
|
|
24
|
+
* Resets the silence counter.
|
|
25
|
+
*/
|
|
26
|
+
ping(): void;
|
|
27
|
+
/** Returns true if the worker pinged recently (within maxSilenceMs). */
|
|
28
|
+
isAlive(): boolean;
|
|
29
|
+
/** Returns the timestamp of the last ping, or null if never pinged. */
|
|
30
|
+
getLastPing(): Date | null;
|
|
31
|
+
private scheduleCheck;
|
|
32
|
+
private check;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=worker-heartbeat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-heartbeat.d.ts","sourceRoot":"","sources":["../../../src/engine/workers/worker-heartbeat.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAa3C;;;;GAIG;AACH,qBAAa,eAAgB,SAAQ,YAAY;IAC/C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAqB;IACrC,OAAO,CAAC,KAAK,CAA8C;IAC3D,OAAO,CAAC,OAAO,CAAS;gBAEZ,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE;IAOxF,uDAAuD;IACvD,KAAK,IAAI,IAAI;IAQb,4DAA4D;IAC5D,IAAI,IAAI,IAAI;IAQZ;;;OAGG;IACH,IAAI,IAAI,IAAI;IAIZ,wEAAwE;IACxE,OAAO,IAAI,OAAO;IAOlB,uEAAuE;IACvE,WAAW,IAAI,IAAI,GAAG,IAAI;IAQ1B,OAAO,CAAC,aAAa;IASrB,OAAO,CAAC,KAAK;CAWd"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// engine/workers/worker-heartbeat.ts — Heartbeat monitor for background workers (SPEC-137)
|
|
2
|
+
import { EventEmitter } from 'node:events';
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Constants
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
const DEFAULT_INTERVAL_MS = 30_000; // 30 seconds
|
|
7
|
+
const DEFAULT_MAX_SILENCE_MS = 90_000; // 3 x interval
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// WorkerHeartbeat
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
/**
|
|
12
|
+
* Monitors a single worker via periodic ping checks.
|
|
13
|
+
* Emits 'worker-dead' when the worker has been silent beyond maxSilenceMs.
|
|
14
|
+
* Uses setTimeout loops (not setInterval) for clean teardown.
|
|
15
|
+
*/
|
|
16
|
+
export class WorkerHeartbeat extends EventEmitter {
|
|
17
|
+
workerName;
|
|
18
|
+
intervalMs;
|
|
19
|
+
maxSilenceMs;
|
|
20
|
+
lastPing = null;
|
|
21
|
+
timer = null;
|
|
22
|
+
running = false;
|
|
23
|
+
constructor(workerName, options) {
|
|
24
|
+
super();
|
|
25
|
+
this.workerName = workerName;
|
|
26
|
+
this.intervalMs = options?.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
27
|
+
this.maxSilenceMs = options?.maxSilenceMs ?? DEFAULT_MAX_SILENCE_MS;
|
|
28
|
+
}
|
|
29
|
+
/** Start the heartbeat monitoring loop. Idempotent. */
|
|
30
|
+
start() {
|
|
31
|
+
if (this.running) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
this.running = true;
|
|
35
|
+
this.scheduleCheck();
|
|
36
|
+
}
|
|
37
|
+
/** Stop the heartbeat loop. Safe to call multiple times. */
|
|
38
|
+
stop() {
|
|
39
|
+
this.running = false;
|
|
40
|
+
if (this.timer !== null) {
|
|
41
|
+
clearTimeout(this.timer);
|
|
42
|
+
this.timer = null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Called by the worker process to signal it is alive.
|
|
47
|
+
* Resets the silence counter.
|
|
48
|
+
*/
|
|
49
|
+
ping() {
|
|
50
|
+
this.lastPing = new Date();
|
|
51
|
+
}
|
|
52
|
+
/** Returns true if the worker pinged recently (within maxSilenceMs). */
|
|
53
|
+
isAlive() {
|
|
54
|
+
if (this.lastPing === null) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return Date.now() - this.lastPing.getTime() < this.maxSilenceMs;
|
|
58
|
+
}
|
|
59
|
+
/** Returns the timestamp of the last ping, or null if never pinged. */
|
|
60
|
+
getLastPing() {
|
|
61
|
+
return this.lastPing;
|
|
62
|
+
}
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Private
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
scheduleCheck() {
|
|
67
|
+
if (!this.running) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
this.timer = setTimeout(() => {
|
|
71
|
+
this.check();
|
|
72
|
+
}, this.intervalMs);
|
|
73
|
+
}
|
|
74
|
+
check() {
|
|
75
|
+
if (!this.running) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (this.lastPing !== null && !this.isAlive()) {
|
|
79
|
+
this.emit('worker-dead', this.workerName);
|
|
80
|
+
}
|
|
81
|
+
this.scheduleCheck();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=worker-heartbeat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-heartbeat.js","sourceRoot":"","sources":["../../../src/engine/workers/worker-heartbeat.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAE3F,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,8EAA8E;AAC9E,YAAY;AACZ,8EAA8E;AAE9E,MAAM,mBAAmB,GAAG,MAAM,CAAC,CAAC,aAAa;AACjD,MAAM,sBAAsB,GAAG,MAAM,CAAC,CAAC,eAAe;AAEtD,8EAA8E;AAC9E,kBAAkB;AAClB,8EAA8E;AAE9E;;;;GAIG;AACH,MAAM,OAAO,eAAgB,SAAQ,YAAY;IAC9B,UAAU,CAAS;IACnB,UAAU,CAAS;IACnB,YAAY,CAAS;IAC9B,QAAQ,GAAgB,IAAI,CAAC;IAC7B,KAAK,GAAyC,IAAI,CAAC;IACnD,OAAO,GAAG,KAAK,CAAC;IAExB,YAAY,UAAkB,EAAE,OAAwD;QACtF,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,UAAU,GAAG,OAAO,EAAE,UAAU,IAAI,mBAAmB,CAAC;QAC7D,IAAI,CAAC,YAAY,GAAG,OAAO,EAAE,YAAY,IAAI,sBAAsB,CAAC;IACtE,CAAC;IAED,uDAAuD;IACvD,KAAK;QACH,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,OAAO;QACT,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,CAAC,aAAa,EAAE,CAAC;IACvB,CAAC;IAED,4DAA4D;IAC5D,IAAI;QACF,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC;QACrB,IAAI,IAAI,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;YACxB,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACzB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QACpB,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,IAAI;QACF,IAAI,CAAC,QAAQ,GAAG,IAAI,IAAI,EAAE,CAAC;IAC7B,CAAC;IAED,wEAAwE;IACxE,OAAO;QACL,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;YAC3B,OAAO,KAAK,CAAC;QACf,CAAC;QACD,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,YAAY,CAAC;IAClE,CAAC;IAED,uEAAuE;IACvE,WAAW;QACT,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED,8EAA8E;IAC9E,UAAU;IACV,8EAA8E;IAEtE,aAAa;QACnB,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC3B,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IACtB,CAAC;IAEO,KAAK;QACX,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;QAED,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC;YAC9C,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QAC5C,CAAC;QAED,IAAI,CAAC,aAAa,EAAE,CAAC;IACvB,CAAC;CACF"}
|
package/dist/index.js
CHANGED
|
@@ -114,6 +114,12 @@ import { registerStatusTool } from './tools/register-status-tool.js';
|
|
|
114
114
|
// Server setup
|
|
115
115
|
// ---------------------------------------------------------------------------
|
|
116
116
|
const SERVER_INSTRUCTIONS = [
|
|
117
|
+
'CONTEXT — Why structured specs matter:',
|
|
118
|
+
'- Vibe Coding (describing features informally to AI) creates technical debt at scale',
|
|
119
|
+
'- The bottleneck has moved from writing code to validating whether generated code is correct',
|
|
120
|
+
'- Spec Driven Development is BDD for the AI era: specify behavior before implementation',
|
|
121
|
+
'- Planu enforces this: spec first, then implement, then validate — no exceptions',
|
|
122
|
+
'',
|
|
117
123
|
'You are Planu — a friendly assistant that helps people plan and organize software projects.',
|
|
118
124
|
'',
|
|
119
125
|
'AUTONOMOUS WORKFLOW — zero configuration required:',
|