@soleri/core 2.1.0 → 2.4.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/brain.d.ts +3 -1
- package/dist/brain/brain.d.ts.map +1 -1
- package/dist/brain/brain.js +60 -4
- package/dist/brain/brain.js.map +1 -1
- package/dist/brain/intelligence.d.ts +36 -1
- package/dist/brain/intelligence.d.ts.map +1 -1
- package/dist/brain/intelligence.js +119 -14
- package/dist/brain/intelligence.js.map +1 -1
- package/dist/brain/types.d.ts +32 -0
- package/dist/brain/types.d.ts.map +1 -1
- package/dist/control/identity-manager.d.ts +22 -0
- package/dist/control/identity-manager.d.ts.map +1 -0
- package/dist/control/identity-manager.js +233 -0
- package/dist/control/identity-manager.js.map +1 -0
- package/dist/control/intent-router.d.ts +32 -0
- package/dist/control/intent-router.d.ts.map +1 -0
- package/dist/control/intent-router.js +242 -0
- package/dist/control/intent-router.js.map +1 -0
- package/dist/control/types.d.ts +68 -0
- package/dist/control/types.d.ts.map +1 -0
- package/dist/control/types.js +9 -0
- package/dist/control/types.js.map +1 -0
- package/dist/curator/curator.d.ts +29 -0
- package/dist/curator/curator.d.ts.map +1 -1
- package/dist/curator/curator.js +135 -0
- package/dist/curator/curator.js.map +1 -1
- package/dist/facades/types.d.ts +1 -1
- package/dist/governance/governance.d.ts +42 -0
- package/dist/governance/governance.d.ts.map +1 -0
- package/dist/governance/governance.js +488 -0
- package/dist/governance/governance.js.map +1 -0
- package/dist/governance/index.d.ts +3 -0
- package/dist/governance/index.d.ts.map +1 -0
- package/dist/governance/index.js +2 -0
- package/dist/governance/index.js.map +1 -0
- package/dist/governance/types.d.ts +102 -0
- package/dist/governance/types.d.ts.map +1 -0
- package/dist/governance/types.js +3 -0
- package/dist/governance/types.js.map +1 -0
- package/dist/index.d.ts +32 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +29 -1
- package/dist/index.js.map +1 -1
- package/dist/logging/logger.d.ts +37 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +145 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/logging/types.d.ts +19 -0
- package/dist/logging/types.d.ts.map +1 -0
- package/dist/logging/types.js +2 -0
- package/dist/logging/types.js.map +1 -0
- package/dist/loop/loop-manager.d.ts +49 -0
- package/dist/loop/loop-manager.d.ts.map +1 -0
- package/dist/loop/loop-manager.js +105 -0
- package/dist/loop/loop-manager.js.map +1 -0
- package/dist/loop/types.d.ts +35 -0
- package/dist/loop/types.d.ts.map +1 -0
- package/dist/loop/types.js +8 -0
- package/dist/loop/types.js.map +1 -0
- package/dist/planning/gap-analysis.d.ts +29 -0
- package/dist/planning/gap-analysis.d.ts.map +1 -0
- package/dist/planning/gap-analysis.js +265 -0
- package/dist/planning/gap-analysis.js.map +1 -0
- package/dist/planning/gap-types.d.ts +29 -0
- package/dist/planning/gap-types.d.ts.map +1 -0
- package/dist/planning/gap-types.js +28 -0
- package/dist/planning/gap-types.js.map +1 -0
- package/dist/planning/planner.d.ts +150 -1
- package/dist/planning/planner.d.ts.map +1 -1
- package/dist/planning/planner.js +365 -2
- package/dist/planning/planner.js.map +1 -1
- package/dist/project/project-registry.d.ts +79 -0
- package/dist/project/project-registry.d.ts.map +1 -0
- package/dist/project/project-registry.js +276 -0
- package/dist/project/project-registry.js.map +1 -0
- package/dist/project/types.d.ts +28 -0
- package/dist/project/types.d.ts.map +1 -0
- package/dist/project/types.js +5 -0
- package/dist/project/types.js.map +1 -0
- package/dist/runtime/admin-extra-ops.d.ts +13 -0
- package/dist/runtime/admin-extra-ops.d.ts.map +1 -0
- package/dist/runtime/admin-extra-ops.js +284 -0
- package/dist/runtime/admin-extra-ops.js.map +1 -0
- package/dist/runtime/admin-ops.d.ts +15 -0
- package/dist/runtime/admin-ops.d.ts.map +1 -0
- package/dist/runtime/admin-ops.js +322 -0
- package/dist/runtime/admin-ops.js.map +1 -0
- package/dist/runtime/capture-ops.d.ts +15 -0
- package/dist/runtime/capture-ops.d.ts.map +1 -0
- package/dist/runtime/capture-ops.js +345 -0
- package/dist/runtime/capture-ops.js.map +1 -0
- package/dist/runtime/core-ops.d.ts +7 -3
- package/dist/runtime/core-ops.d.ts.map +1 -1
- package/dist/runtime/core-ops.js +474 -8
- package/dist/runtime/core-ops.js.map +1 -1
- package/dist/runtime/curator-extra-ops.d.ts +9 -0
- package/dist/runtime/curator-extra-ops.d.ts.map +1 -0
- package/dist/runtime/curator-extra-ops.js +59 -0
- package/dist/runtime/curator-extra-ops.js.map +1 -0
- package/dist/runtime/domain-ops.d.ts.map +1 -1
- package/dist/runtime/domain-ops.js +59 -13
- package/dist/runtime/domain-ops.js.map +1 -1
- package/dist/runtime/grading-ops.d.ts +14 -0
- package/dist/runtime/grading-ops.d.ts.map +1 -0
- package/dist/runtime/grading-ops.js +105 -0
- package/dist/runtime/grading-ops.js.map +1 -0
- package/dist/runtime/loop-ops.d.ts +13 -0
- package/dist/runtime/loop-ops.d.ts.map +1 -0
- package/dist/runtime/loop-ops.js +179 -0
- package/dist/runtime/loop-ops.js.map +1 -0
- package/dist/runtime/memory-cross-project-ops.d.ts +12 -0
- package/dist/runtime/memory-cross-project-ops.d.ts.map +1 -0
- package/dist/runtime/memory-cross-project-ops.js +165 -0
- package/dist/runtime/memory-cross-project-ops.js.map +1 -0
- package/dist/runtime/memory-extra-ops.d.ts +13 -0
- package/dist/runtime/memory-extra-ops.d.ts.map +1 -0
- package/dist/runtime/memory-extra-ops.js +173 -0
- package/dist/runtime/memory-extra-ops.js.map +1 -0
- package/dist/runtime/orchestrate-ops.d.ts +17 -0
- package/dist/runtime/orchestrate-ops.d.ts.map +1 -0
- package/dist/runtime/orchestrate-ops.js +240 -0
- package/dist/runtime/orchestrate-ops.js.map +1 -0
- package/dist/runtime/planning-extra-ops.d.ts +17 -0
- package/dist/runtime/planning-extra-ops.d.ts.map +1 -0
- package/dist/runtime/planning-extra-ops.js +300 -0
- package/dist/runtime/planning-extra-ops.js.map +1 -0
- package/dist/runtime/project-ops.d.ts +15 -0
- package/dist/runtime/project-ops.d.ts.map +1 -0
- package/dist/runtime/project-ops.js +181 -0
- package/dist/runtime/project-ops.js.map +1 -0
- package/dist/runtime/runtime.d.ts.map +1 -1
- package/dist/runtime/runtime.js +44 -1
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/runtime/types.d.ts +21 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/vault-extra-ops.d.ts +9 -0
- package/dist/runtime/vault-extra-ops.d.ts.map +1 -0
- package/dist/runtime/vault-extra-ops.js +195 -0
- package/dist/runtime/vault-extra-ops.js.map +1 -0
- package/dist/telemetry/telemetry.d.ts +48 -0
- package/dist/telemetry/telemetry.d.ts.map +1 -0
- package/dist/telemetry/telemetry.js +87 -0
- package/dist/telemetry/telemetry.js.map +1 -0
- package/dist/vault/vault.d.ts +94 -0
- package/dist/vault/vault.d.ts.map +1 -1
- package/dist/vault/vault.js +340 -1
- package/dist/vault/vault.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/admin-extra-ops.test.ts +420 -0
- package/src/__tests__/admin-ops.test.ts +271 -0
- package/src/__tests__/brain-intelligence.test.ts +205 -0
- package/src/__tests__/brain.test.ts +131 -0
- package/src/__tests__/capture-ops.test.ts +509 -0
- package/src/__tests__/core-ops.test.ts +266 -2
- package/src/__tests__/curator-extra-ops.test.ts +359 -0
- package/src/__tests__/domain-ops.test.ts +66 -0
- package/src/__tests__/governance.test.ts +522 -0
- package/src/__tests__/grading-ops.test.ts +340 -0
- package/src/__tests__/identity-manager.test.ts +243 -0
- package/src/__tests__/intent-router.test.ts +222 -0
- package/src/__tests__/logger.test.ts +200 -0
- package/src/__tests__/loop-ops.test.ts +398 -0
- package/src/__tests__/memory-cross-project-ops.test.ts +246 -0
- package/src/__tests__/memory-extra-ops.test.ts +352 -0
- package/src/__tests__/orchestrate-ops.test.ts +284 -0
- package/src/__tests__/planner.test.ts +331 -0
- package/src/__tests__/planning-extra-ops.test.ts +548 -0
- package/src/__tests__/project-ops.test.ts +367 -0
- package/src/__tests__/vault-extra-ops.test.ts +407 -0
- package/src/brain/brain.ts +114 -7
- package/src/brain/intelligence.ts +179 -10
- package/src/brain/types.ts +38 -0
- package/src/control/identity-manager.ts +354 -0
- package/src/control/intent-router.ts +326 -0
- package/src/control/types.ts +102 -0
- package/src/curator/curator.ts +213 -0
- package/src/governance/governance.ts +698 -0
- package/src/governance/index.ts +18 -0
- package/src/governance/types.ts +111 -0
- package/src/index.ts +102 -2
- package/src/logging/logger.ts +154 -0
- package/src/logging/types.ts +21 -0
- package/src/loop/loop-manager.ts +130 -0
- package/src/loop/types.ts +44 -0
- package/src/planning/gap-analysis.ts +506 -0
- package/src/planning/gap-types.ts +58 -0
- package/src/planning/planner.ts +478 -2
- package/src/project/project-registry.ts +358 -0
- package/src/project/types.ts +31 -0
- package/src/runtime/admin-extra-ops.ts +307 -0
- package/src/runtime/admin-ops.ts +329 -0
- package/src/runtime/capture-ops.ts +385 -0
- package/src/runtime/core-ops.ts +535 -7
- package/src/runtime/curator-extra-ops.ts +71 -0
- package/src/runtime/domain-ops.ts +65 -13
- package/src/runtime/grading-ops.ts +121 -0
- package/src/runtime/loop-ops.ts +194 -0
- package/src/runtime/memory-cross-project-ops.ts +192 -0
- package/src/runtime/memory-extra-ops.ts +186 -0
- package/src/runtime/orchestrate-ops.ts +272 -0
- package/src/runtime/planning-extra-ops.ts +327 -0
- package/src/runtime/project-ops.ts +196 -0
- package/src/runtime/runtime.ts +49 -1
- package/src/runtime/types.ts +21 -0
- package/src/runtime/vault-extra-ops.ts +225 -0
- package/src/telemetry/telemetry.ts +118 -0
- package/src/vault/vault.ts +412 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
2
|
import { Planner } from '../planning/planner.js';
|
|
3
|
+
import type { PlanGap } from '../planning/gap-types.js';
|
|
4
|
+
import { generateGapId } from '../planning/gap-types.js';
|
|
3
5
|
import { mkdirSync, rmSync } from 'node:fs';
|
|
4
6
|
import { join } from 'node:path';
|
|
5
7
|
import { tmpdir } from 'node:os';
|
|
@@ -225,6 +227,335 @@ describe('Planner', () => {
|
|
|
225
227
|
});
|
|
226
228
|
});
|
|
227
229
|
|
|
230
|
+
describe('grade', () => {
|
|
231
|
+
it('should grade a well-formed plan highly on first iteration', () => {
|
|
232
|
+
const plan = planner.create({
|
|
233
|
+
objective: 'Implement a Redis caching layer for the API to reduce DB load by 50%',
|
|
234
|
+
scope: 'Backend API services only. Does not include frontend caching or CDN.',
|
|
235
|
+
decisions: [
|
|
236
|
+
'Use Redis because it provides sub-millisecond latency and supports TTL natively',
|
|
237
|
+
'Set TTL to 5 minutes since average data freshness requirement is 10 minutes',
|
|
238
|
+
],
|
|
239
|
+
tasks: [
|
|
240
|
+
{ title: 'Set up Redis client', description: 'Install and configure Redis connection pool' },
|
|
241
|
+
{ title: 'Add cache middleware', description: 'Express middleware for transparent caching' },
|
|
242
|
+
{ title: 'Add invalidation logic', description: 'Purge cache on write operations to ensure consistency' },
|
|
243
|
+
{ title: 'Write integration tests', description: 'Test cache hit/miss scenarios with Redis' },
|
|
244
|
+
{ title: 'Add monitoring', description: 'Track and verify cache hit rate metrics' },
|
|
245
|
+
],
|
|
246
|
+
});
|
|
247
|
+
const check = planner.grade(plan.id);
|
|
248
|
+
// Iteration 1: minor gaps are free, so well-formed plan scores very high
|
|
249
|
+
expect(check.score).toBeGreaterThanOrEqual(95);
|
|
250
|
+
expect(check.grade).toMatch(/^A/);
|
|
251
|
+
expect(check.iteration).toBe(1);
|
|
252
|
+
expect(check.checkId).toMatch(/^chk-/);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('should give low score to empty plan', () => {
|
|
256
|
+
const plan = planner.create({ objective: '', scope: '' });
|
|
257
|
+
const check = planner.grade(plan.id);
|
|
258
|
+
// Missing objective (critical=30) + scope (critical=30) + no tasks (critical=30) = 90 deduction
|
|
259
|
+
expect(check.score).toBeLessThanOrEqual(10);
|
|
260
|
+
expect(check.grade).toBe('F');
|
|
261
|
+
expect(check.gaps.length).toBeGreaterThan(0);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('should use severity-weighted scoring', () => {
|
|
265
|
+
// Plan with 1 critical gap (missing tasks) = -30 points
|
|
266
|
+
const plan = planner.create({
|
|
267
|
+
objective: 'Good objective with some detail',
|
|
268
|
+
scope: 'Narrow scope that excludes nothing important',
|
|
269
|
+
});
|
|
270
|
+
const check = planner.grade(plan.id);
|
|
271
|
+
// No tasks = critical (-30), but also minor gaps from completeness/semantic
|
|
272
|
+
// On iteration 1, minor gaps are free, so only critical gap counts
|
|
273
|
+
expect(check.score).toBe(70); // 100 - 30 (no tasks)
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('should detect duplicate task titles', () => {
|
|
277
|
+
const plan = planner.create({
|
|
278
|
+
objective: 'Test duplicate detection in plan grading system',
|
|
279
|
+
scope: 'Testing only, does not affect production',
|
|
280
|
+
decisions: ['Use approach A because it handles edge cases better due to type safety'],
|
|
281
|
+
tasks: [
|
|
282
|
+
{ title: 'Same title', description: 'First task with description' },
|
|
283
|
+
{ title: 'Same title', description: 'Second task with description' },
|
|
284
|
+
{ title: 'Unique title', description: 'Third task with description' },
|
|
285
|
+
],
|
|
286
|
+
});
|
|
287
|
+
const check = planner.grade(plan.id);
|
|
288
|
+
const dupGap = check.gaps.find((g) => g.description.includes('Duplicate'));
|
|
289
|
+
expect(dupGap).toBeDefined();
|
|
290
|
+
expect(dupGap!.category).toBe('semantic-quality');
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('should detect tasks with short/missing descriptions', () => {
|
|
294
|
+
const plan = planner.create({
|
|
295
|
+
objective: 'Test description detection in plan grading system',
|
|
296
|
+
scope: 'Testing only, does not affect production',
|
|
297
|
+
decisions: ['Use assertions because they provide clear feedback on failures'],
|
|
298
|
+
tasks: [
|
|
299
|
+
{ title: 'Task with desc', description: 'Has a proper description' },
|
|
300
|
+
{ title: 'Task without desc', description: '' },
|
|
301
|
+
{ title: 'Another task', description: 'Also has a description' },
|
|
302
|
+
],
|
|
303
|
+
});
|
|
304
|
+
const check = planner.grade(plan.id);
|
|
305
|
+
const descGap = check.gaps.find((g) => g.description.includes('short descriptions'));
|
|
306
|
+
expect(descGap).toBeDefined();
|
|
307
|
+
expect(descGap!.category).toBe('clarity');
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('should track iteration number across multiple grades', () => {
|
|
311
|
+
const plan = planner.create({
|
|
312
|
+
objective: 'Iteration tracking test plan',
|
|
313
|
+
scope: 'Test scope',
|
|
314
|
+
});
|
|
315
|
+
const check1 = planner.grade(plan.id);
|
|
316
|
+
const check2 = planner.grade(plan.id);
|
|
317
|
+
const check3 = planner.grade(plan.id);
|
|
318
|
+
expect(check1.iteration).toBe(1);
|
|
319
|
+
expect(check2.iteration).toBe(2);
|
|
320
|
+
expect(check3.iteration).toBe(3);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should apply iteration leniency — minor gaps free on iter 1', () => {
|
|
324
|
+
// Plan with only minor gaps: no metrics in objective, no exclusions in scope
|
|
325
|
+
const plan = planner.create({
|
|
326
|
+
objective: 'Build a comprehensive authentication system for the application',
|
|
327
|
+
scope: 'Backend authentication module',
|
|
328
|
+
decisions: [
|
|
329
|
+
'Use JWT tokens because they are stateless and work well with microservices',
|
|
330
|
+
],
|
|
331
|
+
tasks: [
|
|
332
|
+
{ title: 'Create auth middleware', description: 'JWT validation middleware for Express' },
|
|
333
|
+
{ title: 'Add login endpoint', description: 'POST /auth/login with credential validation' },
|
|
334
|
+
{ title: 'Add refresh tokens', description: 'Implement token refresh flow with rotation' },
|
|
335
|
+
{ title: 'Write auth tests', description: 'Integration tests for all auth endpoints' },
|
|
336
|
+
],
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// Iteration 1: minor gaps free → score should be 100
|
|
340
|
+
const check1 = planner.grade(plan.id);
|
|
341
|
+
expect(check1.score).toBe(100);
|
|
342
|
+
|
|
343
|
+
// Iteration 2: minor gaps at half weight → score slightly lower
|
|
344
|
+
const check2 = planner.grade(plan.id);
|
|
345
|
+
expect(check2.score).toBeLessThan(check1.score);
|
|
346
|
+
|
|
347
|
+
// Iteration 3: minor gaps at full weight → score even lower
|
|
348
|
+
const check3 = planner.grade(plan.id);
|
|
349
|
+
expect(check3.score).toBeLessThanOrEqual(check2.score);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it('should cap category deductions', () => {
|
|
353
|
+
// Plan with many clarity issues (ambiguous words) — capped at 10
|
|
354
|
+
const plan = planner.create({
|
|
355
|
+
objective: 'Maybe perhaps build something simple and easy, possibly soon, etc',
|
|
356
|
+
scope: 'Various things, probably several modules, somehow',
|
|
357
|
+
decisions: [
|
|
358
|
+
'Use some appropriate approach because it seems good due to various reasons',
|
|
359
|
+
],
|
|
360
|
+
tasks: [
|
|
361
|
+
{ title: 'Do some stuff', description: 'Maybe implement various things somehow' },
|
|
362
|
+
{ title: 'Maybe test', description: 'Perhaps write some tests probably' },
|
|
363
|
+
{ title: 'Maybe deploy', description: 'Possibly deploy to various environments soon' },
|
|
364
|
+
],
|
|
365
|
+
});
|
|
366
|
+
// Grade on iteration 3 to get full minor weight
|
|
367
|
+
planner.grade(plan.id);
|
|
368
|
+
planner.grade(plan.id);
|
|
369
|
+
const check3 = planner.grade(plan.id);
|
|
370
|
+
// Clarity category should be capped at 10 even though there are many ambiguous words
|
|
371
|
+
// Without cap, multiple minor clarity gaps (3x2=6, but also semantic-quality gaps)
|
|
372
|
+
// The key assertion: score shouldn't be destroyed by clarity alone
|
|
373
|
+
expect(check3.score).toBeGreaterThanOrEqual(50);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it('should store check in plan history', () => {
|
|
377
|
+
const plan = planner.create({ objective: 'History test plan objective', scope: 'test scope' });
|
|
378
|
+
planner.grade(plan.id);
|
|
379
|
+
planner.grade(plan.id);
|
|
380
|
+
const history = planner.getCheckHistory(plan.id);
|
|
381
|
+
expect(history).toHaveLength(2);
|
|
382
|
+
expect(history[0].checkId).not.toBe(history[1].checkId);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it('should persist latestCheck', () => {
|
|
386
|
+
const plan = planner.create({ objective: 'Persist test plan objective', scope: 'test scope' });
|
|
387
|
+
const check = planner.grade(plan.id);
|
|
388
|
+
const latest = planner.getLatestCheck(plan.id);
|
|
389
|
+
expect(latest).not.toBeNull();
|
|
390
|
+
expect(latest!.checkId).toBe(check.checkId);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
it('should detect circular dependencies', () => {
|
|
394
|
+
const plan = planner.create({
|
|
395
|
+
objective: 'Circular dependency detection test plan',
|
|
396
|
+
scope: 'Test scope only, does not affect production',
|
|
397
|
+
decisions: ['Test with circular deps because it validates the analysis engine'],
|
|
398
|
+
tasks: [
|
|
399
|
+
{ title: 'Task A', description: 'First task in the cycle' },
|
|
400
|
+
{ title: 'Task B', description: 'Second task in the cycle' },
|
|
401
|
+
{ title: 'Task C', description: 'Third task (not in cycle)' },
|
|
402
|
+
],
|
|
403
|
+
});
|
|
404
|
+
// Manually create circular deps
|
|
405
|
+
const p = planner.get(plan.id)!;
|
|
406
|
+
p.tasks[0].dependsOn = ['task-2'];
|
|
407
|
+
p.tasks[1].dependsOn = ['task-1'];
|
|
408
|
+
const check = planner.grade(plan.id);
|
|
409
|
+
const circGap = check.gaps.find((g) => g.description.includes('Circular'));
|
|
410
|
+
expect(circGap).toBeDefined();
|
|
411
|
+
expect(circGap!.severity).toBe('critical');
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
it('should use correct grade thresholds: A+=95, A=90, B=80, C=70, D=60', () => {
|
|
415
|
+
// We can verify by creating plans with known gap profiles
|
|
416
|
+
// Plan with 1 major gap = score 85 → grade B (80-89)
|
|
417
|
+
const plan = planner.create({
|
|
418
|
+
objective: 'Test threshold plan with a good objective description',
|
|
419
|
+
scope: 'Narrow scope, does not include anything beyond testing',
|
|
420
|
+
decisions: [], // no decisions = major gap from semantic-quality (-15)
|
|
421
|
+
tasks: [
|
|
422
|
+
{ title: 'Task 1', description: 'First detailed task description' },
|
|
423
|
+
{ title: 'Task 2', description: 'Second detailed task description' },
|
|
424
|
+
{ title: 'Task 3', description: 'Third detailed task description' },
|
|
425
|
+
],
|
|
426
|
+
});
|
|
427
|
+
const check = planner.grade(plan.id);
|
|
428
|
+
// 1 major gap (no decisions for 3 tasks) = -15, iter 1 minor gaps free
|
|
429
|
+
expect(check.score).toBe(85);
|
|
430
|
+
expect(check.grade).toBe('B');
|
|
431
|
+
});
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
describe('meetsGrade', () => {
|
|
435
|
+
it('should return true when plan meets target grade', () => {
|
|
436
|
+
const plan = planner.create({
|
|
437
|
+
objective: 'Build a comprehensive feature for the testing module',
|
|
438
|
+
scope: 'Testing module only, does not include deployment',
|
|
439
|
+
decisions: ['Use vitest because it integrates well with TypeScript due to native support'],
|
|
440
|
+
tasks: [
|
|
441
|
+
{ title: 'Write unit tests', description: 'Cover all edge cases in auth module' },
|
|
442
|
+
{ title: 'Write integration tests', description: 'End-to-end API tests for auth flow' },
|
|
443
|
+
{ title: 'Add CI pipeline', description: 'Run tests on every PR automatically' },
|
|
444
|
+
{ title: 'Add coverage report', description: 'Track and verify code coverage metrics' },
|
|
445
|
+
],
|
|
446
|
+
});
|
|
447
|
+
const result = planner.meetsGrade(plan.id, 'B');
|
|
448
|
+
expect(result.meets).toBe(true);
|
|
449
|
+
expect(result.check.score).toBeGreaterThanOrEqual(80);
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
it('should return false when plan does not meet target grade', () => {
|
|
453
|
+
const plan = planner.create({ objective: '', scope: '' });
|
|
454
|
+
const result = planner.meetsGrade(plan.id, 'A+');
|
|
455
|
+
expect(result.meets).toBe(false);
|
|
456
|
+
});
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
describe('getCheckHistory', () => {
|
|
460
|
+
it('should return empty array for plan with no checks', () => {
|
|
461
|
+
const plan = planner.create({ objective: 'No checks plan', scope: 'test scope' });
|
|
462
|
+
expect(planner.getCheckHistory(plan.id)).toEqual([]);
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it('should throw for unknown plan', () => {
|
|
466
|
+
expect(() => planner.getCheckHistory('plan-nonexistent')).toThrow('not found');
|
|
467
|
+
});
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
describe('getLatestCheck', () => {
|
|
471
|
+
it('should return null for plan with no checks', () => {
|
|
472
|
+
const plan = planner.create({ objective: 'No checks plan', scope: 'test scope' });
|
|
473
|
+
expect(planner.getLatestCheck(plan.id)).toBeNull();
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it('should throw for unknown plan', () => {
|
|
477
|
+
expect(() => planner.getLatestCheck('plan-nonexistent')).toThrow('not found');
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
describe('custom gap analysis passes', () => {
|
|
482
|
+
it('should run custom passes alongside built-in ones', () => {
|
|
483
|
+
const customPass = (plan: { objective: string }): PlanGap[] => {
|
|
484
|
+
if (plan.objective.includes('TODO')) {
|
|
485
|
+
return [
|
|
486
|
+
{
|
|
487
|
+
id: generateGapId(),
|
|
488
|
+
severity: 'major',
|
|
489
|
+
category: 'semantic-quality',
|
|
490
|
+
description: 'Objective contains TODO — not ready for grading.',
|
|
491
|
+
recommendation: 'Resolve all TODOs before grading the plan.',
|
|
492
|
+
location: 'objective',
|
|
493
|
+
_trigger: 'custom_todo_check',
|
|
494
|
+
},
|
|
495
|
+
];
|
|
496
|
+
}
|
|
497
|
+
return [];
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
const customPlanner = new Planner(join(tempDir, 'custom-plans.json'), {
|
|
501
|
+
customPasses: [customPass],
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
// Plan with TODO should get the custom gap
|
|
505
|
+
const plan = customPlanner.create({
|
|
506
|
+
objective: 'TODO: flesh out this objective for the project',
|
|
507
|
+
scope: 'Backend services only. Does not include frontend.',
|
|
508
|
+
decisions: ['Use TypeScript because it provides type safety due to static analysis'],
|
|
509
|
+
tasks: [
|
|
510
|
+
{ title: 'Task A', description: 'First implementation task' },
|
|
511
|
+
{ title: 'Task B', description: 'Second implementation task' },
|
|
512
|
+
{ title: 'Task C', description: 'Third implementation task' },
|
|
513
|
+
],
|
|
514
|
+
});
|
|
515
|
+
const check = customPlanner.grade(plan.id);
|
|
516
|
+
const todoGap = check.gaps.find((g) => g._trigger === 'custom_todo_check');
|
|
517
|
+
expect(todoGap).toBeDefined();
|
|
518
|
+
expect(todoGap!.severity).toBe('major');
|
|
519
|
+
// Score should reflect the -15 from the major custom gap
|
|
520
|
+
expect(check.score).toBeLessThan(100);
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
it('should not fire custom gaps when condition is not met', () => {
|
|
524
|
+
const customPass = (plan: { objective: string }): PlanGap[] => {
|
|
525
|
+
if (plan.objective.includes('TODO')) {
|
|
526
|
+
return [
|
|
527
|
+
{
|
|
528
|
+
id: generateGapId(),
|
|
529
|
+
severity: 'major',
|
|
530
|
+
category: 'semantic-quality',
|
|
531
|
+
description: 'Contains TODO',
|
|
532
|
+
recommendation: 'Fix it',
|
|
533
|
+
},
|
|
534
|
+
];
|
|
535
|
+
}
|
|
536
|
+
return [];
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
const customPlanner = new Planner(join(tempDir, 'custom-plans2.json'), {
|
|
540
|
+
customPasses: [customPass],
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
const plan = customPlanner.create({
|
|
544
|
+
objective: 'Build a clean authentication system for the API endpoints',
|
|
545
|
+
scope: 'Backend services only. Does not include frontend or mobile.',
|
|
546
|
+
decisions: ['Use JWT because it is stateless and works with microservices'],
|
|
547
|
+
tasks: [
|
|
548
|
+
{ title: 'Auth middleware', description: 'Create JWT validation middleware' },
|
|
549
|
+
{ title: 'Login endpoint', description: 'POST /auth/login with credentials' },
|
|
550
|
+
{ title: 'Refresh tokens', description: 'Token refresh flow with rotation' },
|
|
551
|
+
],
|
|
552
|
+
});
|
|
553
|
+
const check = customPlanner.grade(plan.id);
|
|
554
|
+
const todoGap = check.gaps.find((g) => g.description.includes('TODO'));
|
|
555
|
+
expect(todoGap).toBeUndefined();
|
|
556
|
+
});
|
|
557
|
+
});
|
|
558
|
+
|
|
228
559
|
describe('full lifecycle', () => {
|
|
229
560
|
it('should support draft → approved → executing → completed with tasks', () => {
|
|
230
561
|
const plan = planner.create({
|