@cleocode/core 2026.4.5 → 2026.4.6
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/discovery.d.ts +69 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1643 -2349
- package/dist/index.js.map +4 -4
- package/dist/init.d.ts +51 -0
- package/dist/init.d.ts.map +1 -1
- package/dist/internal.d.ts +9 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/lifecycle/default-chain.d.ts +8 -2
- package/dist/lifecycle/default-chain.d.ts.map +1 -1
- package/dist/lifecycle/index.d.ts +1 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/stage-guidance.d.ts +140 -0
- package/dist/lifecycle/stage-guidance.d.ts.map +1 -0
- package/dist/orchestration/protocol-validators.d.ts +122 -3
- package/dist/orchestration/protocol-validators.d.ts.map +1 -1
- package/dist/paths.d.ts +91 -0
- package/dist/paths.d.ts.map +1 -1
- package/dist/scaffold.d.ts +31 -1
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/skills/dispatch.d.ts +1 -1
- package/dist/skills/skill-paths.d.ts +9 -6
- package/dist/skills/skill-paths.d.ts.map +1 -1
- package/dist/validation/protocols/_shared.d.ts +40 -0
- package/dist/validation/protocols/_shared.d.ts.map +1 -0
- package/dist/validation/protocols/architecture-decision.d.ts +23 -0
- package/dist/validation/protocols/architecture-decision.d.ts.map +1 -0
- package/dist/validation/protocols/artifact-publish.d.ts +22 -0
- package/dist/validation/protocols/artifact-publish.d.ts.map +1 -0
- package/dist/validation/protocols/consensus.d.ts +11 -17
- package/dist/validation/protocols/consensus.d.ts.map +1 -1
- package/dist/validation/protocols/contribution.d.ts +12 -17
- package/dist/validation/protocols/contribution.d.ts.map +1 -1
- package/dist/validation/protocols/decomposition.d.ts +18 -21
- package/dist/validation/protocols/decomposition.d.ts.map +1 -1
- package/dist/validation/protocols/implementation.d.ts +9 -17
- package/dist/validation/protocols/implementation.d.ts.map +1 -1
- package/dist/validation/protocols/provenance.d.ts +23 -0
- package/dist/validation/protocols/provenance.d.ts.map +1 -0
- package/dist/validation/protocols/release.d.ts +25 -0
- package/dist/validation/protocols/release.d.ts.map +1 -0
- package/dist/validation/protocols/research.d.ts +9 -17
- package/dist/validation/protocols/research.d.ts.map +1 -1
- package/dist/validation/protocols/specification.d.ts +7 -17
- package/dist/validation/protocols/specification.d.ts.map +1 -1
- package/dist/validation/protocols/testing.d.ts +22 -0
- package/dist/validation/protocols/testing.d.ts.map +1 -0
- package/dist/validation/protocols/validation.d.ts +22 -0
- package/dist/validation/protocols/validation.d.ts.map +1 -0
- package/package.json +7 -7
- package/src/__tests__/injection-mvi-tiers.test.js +54 -90
- package/src/__tests__/injection-mvi-tiers.test.js.map +1 -1
- package/src/discovery.ts +235 -0
- package/src/hooks/handlers/__tests__/hook-automation-e2e.test.js +3 -1
- package/src/hooks/handlers/__tests__/hook-automation-e2e.test.js.map +1 -1
- package/src/index.ts +16 -0
- package/src/init.ts +196 -0
- package/src/internal.ts +31 -1
- package/src/lifecycle/default-chain.ts +11 -2
- package/src/lifecycle/index.ts +10 -0
- package/src/lifecycle/stage-guidance.ts +282 -0
- package/src/metrics/__tests__/provider-detection.test.js +19 -7
- package/src/metrics/__tests__/provider-detection.test.js.map +1 -1
- package/src/orchestration/__tests__/protocol-validators.test.js +228 -8
- package/src/orchestration/__tests__/protocol-validators.test.js.map +1 -1
- package/src/orchestration/__tests__/protocol-validators.test.ts +259 -7
- package/src/orchestration/protocol-validators.ts +419 -4
- package/src/paths.ts +110 -0
- package/src/scaffold.ts +240 -4
- package/src/skills/dispatch.ts +6 -6
- package/src/skills/skill-paths.ts +27 -23
- package/src/validation/protocols/_shared.ts +88 -0
- package/src/validation/protocols/architecture-decision.ts +52 -0
- package/src/validation/protocols/artifact-publish.ts +49 -0
- package/src/validation/protocols/consensus.ts +44 -74
- package/src/validation/protocols/contribution.ts +28 -65
- package/src/validation/protocols/decomposition.ts +37 -64
- package/src/validation/protocols/implementation.ts +25 -65
- package/src/validation/protocols/protocols-markdown/architecture-decision.md +303 -0
- package/src/validation/protocols/protocols-markdown/artifact-publish.md +600 -0
- package/src/validation/protocols/protocols-markdown/consensus.md +322 -0
- package/src/validation/protocols/protocols-markdown/contribution.md +388 -0
- package/src/validation/protocols/protocols-markdown/decomposition.md +421 -0
- package/src/validation/protocols/protocols-markdown/implementation.md +357 -0
- package/src/validation/protocols/protocols-markdown/provenance.md +613 -0
- package/src/validation/protocols/protocols-markdown/release.md +783 -0
- package/src/validation/protocols/protocols-markdown/research.md +261 -0
- package/src/validation/protocols/protocols-markdown/specification.md +300 -0
- package/src/validation/protocols/protocols-markdown/testing.md +287 -0
- package/src/validation/protocols/protocols-markdown/validation.md +242 -0
- package/src/validation/protocols/provenance.ts +50 -0
- package/src/validation/protocols/release.ts +44 -0
- package/src/validation/protocols/research.ts +25 -87
- package/src/validation/protocols/specification.ts +27 -89
- package/src/validation/protocols/testing.ts +46 -0
- package/src/validation/protocols/validation.ts +46 -0
- package/dist/validation/protocols/release-protocol.d.ts +0 -27
- package/dist/validation/protocols/release-protocol.d.ts.map +0 -1
- package/dist/validation/protocols/testing-protocol.d.ts +0 -27
- package/dist/validation/protocols/testing-protocol.d.ts.map +0 -1
- package/dist/validation/protocols/validation-protocol.d.ts +0 -27
- package/dist/validation/protocols/validation-protocol.d.ts.map +0 -1
- package/schemas/agent-configs.schema.json +0 -120
- package/schemas/agent-registry.schema.json +0 -132
- package/schemas/archive.schema.json +0 -450
- package/schemas/brain-decision.schema.json +0 -69
- package/schemas/brain-learning.schema.json +0 -57
- package/schemas/brain-pattern.schema.json +0 -72
- package/schemas/critical-path.schema.json +0 -246
- package/schemas/deps-cache.schema.json +0 -97
- package/schemas/doctor-output.schema.json +0 -283
- package/schemas/error.schema.json +0 -161
- package/schemas/global-config.schema.json +0 -219
- package/schemas/grade.schema.json +0 -49
- package/schemas/log.schema.json +0 -250
- package/schemas/metrics.schema.json +0 -328
- package/schemas/migrations.schema.json +0 -150
- package/schemas/nexus-registry.schema.json +0 -90
- package/schemas/operation-constitution.schema.json +0 -438
- package/schemas/output.schema.json +0 -164
- package/schemas/projects-registry.schema.json +0 -107
- package/schemas/protocol-frontmatter.schema.json +0 -72
- package/schemas/rcasd-consensus-report.schema.json +0 -10
- package/schemas/rcasd-evidence.schema.json +0 -42
- package/schemas/rcasd-gate-result.schema.json +0 -46
- package/schemas/rcasd-hitl-resolution.schema.json +0 -10
- package/schemas/rcasd-index.schema.json +0 -10
- package/schemas/rcasd-manifest.schema.json +0 -10
- package/schemas/rcasd-research-output.schema.json +0 -10
- package/schemas/rcasd-spec-frontmatter.schema.json +0 -10
- package/schemas/rcasd-stage-transition.schema.json +0 -38
- package/schemas/releases.schema.json +0 -267
- package/schemas/skills-manifest.schema.json +0 -91
- package/schemas/spec-index.schema.json +0 -196
- package/schemas/system-flow-atlas.schema.json +0 -125
- package/src/conduit/__tests__/dual-api-e2e.test.d.ts.map +0 -1
- package/src/conduit/__tests__/dual-api-e2e.test.js +0 -178
- package/src/conduit/__tests__/dual-api-e2e.test.js.map +0 -1
- package/src/conduit/__tests__/dual-api-e2e.test.ts +0 -212
- package/src/validation/protocols/release-protocol.ts +0 -80
- package/src/validation/protocols/testing-protocol.ts +0 -93
- package/src/validation/protocols/validation-protocol.ts +0 -93
|
@@ -1,28 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Specification protocol
|
|
2
|
+
* Specification protocol — thin wrapper delegating to the canonical pure validator.
|
|
3
|
+
*
|
|
3
4
|
* @task T4537
|
|
4
|
-
* @
|
|
5
|
+
* @task T260
|
|
5
6
|
*/
|
|
6
|
-
|
|
7
|
-
valid: boolean;
|
|
8
|
-
violations: Array<{
|
|
9
|
-
code: string;
|
|
10
|
-
severity: string;
|
|
11
|
-
message: string;
|
|
12
|
-
}>;
|
|
13
|
-
score: number;
|
|
14
|
-
protocol: string;
|
|
15
|
-
taskId: string;
|
|
16
|
-
}
|
|
7
|
+
import { type ProtocolValidationResult } from '../../orchestration/protocol-validators.js';
|
|
17
8
|
/** Validate specification protocol for a task. */
|
|
18
9
|
export declare function validateSpecificationTask(taskId: string, opts: {
|
|
19
10
|
strict?: boolean;
|
|
20
11
|
specFile?: string;
|
|
21
|
-
}): Promise<
|
|
22
|
-
/** Validate specification protocol from manifest file. */
|
|
12
|
+
}): Promise<ProtocolValidationResult>;
|
|
13
|
+
/** Validate specification protocol from a manifest file. */
|
|
23
14
|
export declare function checkSpecificationManifest(manifestFile: string, opts: {
|
|
24
15
|
strict?: boolean;
|
|
25
16
|
specFile?: string;
|
|
26
|
-
}): Promise<
|
|
27
|
-
export {};
|
|
17
|
+
}): Promise<ProtocolValidationResult>;
|
|
28
18
|
//# sourceMappingURL=specification.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"specification.d.ts","sourceRoot":"","sources":["../../../src/validation/protocols/specification.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"specification.d.ts","sourceRoot":"","sources":["../../../src/validation/protocols/specification.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EACL,KAAK,wBAAwB,EAE9B,MAAM,4CAA4C,CAAC;AAOpD,kDAAkD;AAClD,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,MAAM,EACd,IAAI,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5C,OAAO,CAAC,wBAAwB,CAAC,CAOnC;AAED,4DAA4D;AAC5D,wBAAsB,0BAA0B,CAC9C,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5C,OAAO,CAAC,wBAAwB,CAAC,CAQnC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Testing protocol — thin wrapper delegating to the canonical pure validator.
|
|
3
|
+
*
|
|
4
|
+
* Renamed from testing-protocol.ts in T260. The testing protocol is the
|
|
5
|
+
* project-agnostic IVT loop closure stage: it checks that a detected test
|
|
6
|
+
* framework ran, achieved 100% pass rate, and the Implement→Validate→Test
|
|
7
|
+
* loop converged on the specification. Framework detection and loop logic
|
|
8
|
+
* live in the `ct-ivt-looper` skill; this validator only certifies results.
|
|
9
|
+
*
|
|
10
|
+
* @task T4804
|
|
11
|
+
* @task T260 — drop -protocol suffix, delegate, project-agnostic IVT
|
|
12
|
+
*/
|
|
13
|
+
import { type ProtocolValidationResult, type TestingOptions } from '../../orchestration/protocol-validators.js';
|
|
14
|
+
/** Validate testing protocol for a task. */
|
|
15
|
+
export declare function validateTestingTask(taskId: string, opts: {
|
|
16
|
+
strict?: boolean;
|
|
17
|
+
} & TestingOptions): Promise<ProtocolValidationResult>;
|
|
18
|
+
/** Validate testing protocol from a manifest file. */
|
|
19
|
+
export declare function checkTestingManifest(manifestFile: string, opts: {
|
|
20
|
+
strict?: boolean;
|
|
21
|
+
} & TestingOptions): Promise<ProtocolValidationResult>;
|
|
22
|
+
//# sourceMappingURL=testing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../../../src/validation/protocols/testing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACL,KAAK,wBAAwB,EAC7B,KAAK,cAAc,EAEpB,MAAM,4CAA4C,CAAC;AAOpD,4CAA4C;AAC5C,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,cAAc,GAC1C,OAAO,CAAC,wBAAwB,CAAC,CAKnC;AAED,sDAAsD;AACtD,wBAAsB,oBAAoB,CACxC,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,cAAc,GAC1C,OAAO,CAAC,wBAAwB,CAAC,CAMnC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation protocol — thin wrapper delegating to the canonical pure validator.
|
|
3
|
+
*
|
|
4
|
+
* Renamed from validation-protocol.ts in T260. The validation stage runs
|
|
5
|
+
* static analysis, type checking, and pre-test quality gates. It is the
|
|
6
|
+
* first half of the IVT loop (Implement → Validate → Test); the validator
|
|
7
|
+
* here only verifies that a validation run produced a proper manifest
|
|
8
|
+
* entry — runtime gate enforcement is in the lifecycle state machine.
|
|
9
|
+
*
|
|
10
|
+
* @task T4804
|
|
11
|
+
* @task T260 — drop -protocol suffix, delegate to orchestration validator
|
|
12
|
+
*/
|
|
13
|
+
import { type ProtocolValidationResult, type ValidationStageOptions } from '../../orchestration/protocol-validators.js';
|
|
14
|
+
/** Validate validation-stage protocol for a task. */
|
|
15
|
+
export declare function validateValidationTask(taskId: string, opts: {
|
|
16
|
+
strict?: boolean;
|
|
17
|
+
} & ValidationStageOptions): Promise<ProtocolValidationResult>;
|
|
18
|
+
/** Validate validation-stage protocol from a manifest file. */
|
|
19
|
+
export declare function checkValidationManifest(manifestFile: string, opts: {
|
|
20
|
+
strict?: boolean;
|
|
21
|
+
} & ValidationStageOptions): Promise<ProtocolValidationResult>;
|
|
22
|
+
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../src/validation/protocols/validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACL,KAAK,wBAAwB,EAC7B,KAAK,sBAAsB,EAE5B,MAAM,4CAA4C,CAAC;AAOpD,qDAAqD;AACrD,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,MAAM,EACd,IAAI,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,sBAAsB,GAClD,OAAO,CAAC,wBAAwB,CAAC,CAKnC;AAED,+DAA+D;AAC/D,wBAAsB,uBAAuB,CAC3C,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,sBAAsB,GAClD,OAAO,CAAC,wBAAwB,CAAC,CAMnC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cleocode/core",
|
|
3
|
-
"version": "2026.4.
|
|
3
|
+
"version": "2026.4.6",
|
|
4
4
|
"description": "CLEO core business logic kernel — tasks, sessions, memory, orchestration, lifecycle, with bundled SQLite store",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -35,12 +35,12 @@
|
|
|
35
35
|
"write-file-atomic": "^6.0.0",
|
|
36
36
|
"yaml": "^2.8.2",
|
|
37
37
|
"zod": "^3.25.76",
|
|
38
|
-
"@cleocode/adapters": "2026.4.
|
|
39
|
-
"@cleocode/caamp": "2026.4.
|
|
40
|
-
"@cleocode/contracts": "2026.4.
|
|
41
|
-
"@cleocode/
|
|
42
|
-
"@cleocode/
|
|
43
|
-
"@cleocode/skills": "2026.4.
|
|
38
|
+
"@cleocode/adapters": "2026.4.6",
|
|
39
|
+
"@cleocode/caamp": "2026.4.6",
|
|
40
|
+
"@cleocode/contracts": "2026.4.6",
|
|
41
|
+
"@cleocode/lafs": "2026.4.6",
|
|
42
|
+
"@cleocode/agents": "2026.4.6",
|
|
43
|
+
"@cleocode/skills": "2026.4.6"
|
|
44
44
|
},
|
|
45
45
|
"optionalDependencies": {
|
|
46
46
|
"tree-sitter-c": "^0.24.1",
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Test CLEO-INJECTION.md v2.
|
|
2
|
+
* Test CLEO-INJECTION.md v2.4.0 optimized CLI-only template.
|
|
3
3
|
*
|
|
4
4
|
* Validates the trimmed template:
|
|
5
|
-
* 1. Has version 2.
|
|
6
|
-
* 2. Contains all
|
|
7
|
-
* 3.
|
|
5
|
+
* 1. Has version 2.4.0 with CLI-only dispatch
|
|
6
|
+
* 2. Contains all essential sections (session start, work loop, discovery, memory, errors)
|
|
7
|
+
* 3. Uses `cleo` prefix exclusively (no `ct` prefix, no MCP syntax)
|
|
8
8
|
* 4. Contains escalation section with skill pointers
|
|
9
|
-
* 5.
|
|
9
|
+
* 5. Is under 100 lines (optimized for token efficiency)
|
|
10
10
|
*
|
|
11
11
|
* @task T5096
|
|
12
12
|
*/
|
|
@@ -15,106 +15,84 @@ import { dirname, join, resolve } from 'node:path';
|
|
|
15
15
|
import { fileURLToPath } from 'node:url';
|
|
16
16
|
import { describe, expect, it } from 'vitest';
|
|
17
17
|
const thisFile = fileURLToPath(import.meta.url);
|
|
18
|
-
// Template moved to packages/core/templates/ (was at monorepo root templates/)
|
|
19
|
-
// thisFile is at packages/core/src/__tests__/, go up two levels to reach packages/core/
|
|
20
18
|
const corePackageRoot = resolve(dirname(thisFile), '..', '..');
|
|
21
19
|
const injectionPath = join(corePackageRoot, 'templates', 'CLEO-INJECTION.md');
|
|
22
20
|
const templateExists = existsSync(injectionPath);
|
|
23
|
-
describe('CLEO-INJECTION v2.
|
|
21
|
+
describe('CLEO-INJECTION v2.4.0 CLI-only template', () => {
|
|
24
22
|
const content = templateExists ? readFileSync(injectionPath, 'utf-8') : '';
|
|
25
23
|
it('template file exists at templates/CLEO-INJECTION.md', () => {
|
|
26
24
|
expect(templateExists).toBe(true);
|
|
27
25
|
});
|
|
28
|
-
describe('Version and
|
|
29
|
-
it('has version 2.
|
|
30
|
-
expect(content).toContain('Version: 2.
|
|
26
|
+
describe('Version and identity', () => {
|
|
27
|
+
it('has version 2.4.0', () => {
|
|
28
|
+
expect(content).toContain('Version: 2.4.0');
|
|
31
29
|
});
|
|
32
|
-
it('
|
|
33
|
-
expect(content).toContain('
|
|
34
|
-
});
|
|
35
|
-
});
|
|
36
|
-
describe('Contains all minimal sections', () => {
|
|
37
|
-
it('includes CLEO Identity section', () => {
|
|
38
|
-
expect(content).toContain('## CLEO Identity');
|
|
39
|
-
expect(content).toContain('CLEO protocol agent');
|
|
30
|
+
it('declares CLI-only dispatch', () => {
|
|
31
|
+
expect(content).toContain('CLI-only dispatch');
|
|
40
32
|
expect(content).toContain('cleo <command>');
|
|
41
33
|
});
|
|
42
|
-
|
|
43
|
-
|
|
34
|
+
});
|
|
35
|
+
describe('Contains essential sections', () => {
|
|
36
|
+
it('includes Session Start sequence', () => {
|
|
37
|
+
expect(content).toContain('## Session Start');
|
|
44
38
|
expect(content).toContain('cleo session status');
|
|
45
39
|
expect(content).toContain('cleo dash');
|
|
46
40
|
expect(content).toContain('cleo current');
|
|
47
41
|
expect(content).toContain('cleo next');
|
|
48
42
|
expect(content).toContain('cleo show');
|
|
49
43
|
});
|
|
50
|
-
it('includes
|
|
51
|
-
expect(content).toContain('##
|
|
52
|
-
expect(content).toContain('cleo current');
|
|
44
|
+
it('includes Work Loop', () => {
|
|
45
|
+
expect(content).toContain('## Work Loop');
|
|
53
46
|
expect(content).toContain('cleo complete');
|
|
54
|
-
expect(content).toContain('cleo next');
|
|
55
47
|
});
|
|
56
|
-
it('includes
|
|
57
|
-
expect(content).toContain('##
|
|
58
|
-
expect(content).toContain('
|
|
48
|
+
it('includes Task Discovery', () => {
|
|
49
|
+
expect(content).toContain('## Task Discovery');
|
|
50
|
+
expect(content).toContain('cleo find');
|
|
51
|
+
expect(content).toContain('cleo list');
|
|
52
|
+
});
|
|
53
|
+
it('includes Session Commands', () => {
|
|
54
|
+
expect(content).toContain('## Session Commands');
|
|
55
|
+
expect(content).toContain('cleo briefing');
|
|
56
|
+
});
|
|
57
|
+
it('includes Memory (BRAIN)', () => {
|
|
58
|
+
expect(content).toContain('## Memory (BRAIN)');
|
|
59
|
+
expect(content).toContain('cleo memory find');
|
|
60
|
+
expect(content).toContain('cleo memory timeline');
|
|
61
|
+
expect(content).toContain('cleo memory fetch');
|
|
62
|
+
expect(content).toContain('cleo observe');
|
|
59
63
|
});
|
|
60
64
|
it('includes Error Handling', () => {
|
|
61
65
|
expect(content).toContain('## Error Handling');
|
|
62
66
|
expect(content).toContain('exit code');
|
|
67
|
+
expect(content).toContain('E_NOT_FOUND');
|
|
63
68
|
});
|
|
64
|
-
it('includes
|
|
65
|
-
expect(content).toContain('##
|
|
66
|
-
expect(content).toContain('
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
expect(content).toContain('## Time Estimates Prohibited');
|
|
70
|
-
expect(content).toContain('MUST NOT');
|
|
69
|
+
it('includes Rules', () => {
|
|
70
|
+
expect(content).toContain('## Rules');
|
|
71
|
+
expect(content).toContain('small');
|
|
72
|
+
expect(content).toContain('medium');
|
|
73
|
+
expect(content).toContain('large');
|
|
71
74
|
});
|
|
72
75
|
});
|
|
73
|
-
describe('
|
|
74
|
-
it('does not
|
|
75
|
-
expect(content).not.
|
|
76
|
+
describe('CLI-only — no legacy MCP or ct syntax', () => {
|
|
77
|
+
it('does not use ct prefix for commands', () => {
|
|
78
|
+
expect(content).not.toMatch(/`ct /);
|
|
76
79
|
});
|
|
77
|
-
it('does not contain
|
|
78
|
-
expect(content).not.toContain('
|
|
80
|
+
it('does not contain MCP query/mutate syntax', () => {
|
|
81
|
+
expect(content).not.toContain('query({');
|
|
82
|
+
expect(content).not.toContain('mutate({');
|
|
83
|
+
expect(content).not.toContain('orchestrate.bootstrap');
|
|
79
84
|
});
|
|
80
|
-
it('does not contain
|
|
81
|
-
expect(content).not.
|
|
82
|
-
expect(content).not.toContain('RCASD-IVTR+C Lifecycle');
|
|
85
|
+
it('does not contain TIER markers', () => {
|
|
86
|
+
expect(content).not.toMatch(/<!-- TIER:\w+ -->/);
|
|
83
87
|
});
|
|
84
|
-
it('does not contain
|
|
88
|
+
it('does not contain removed standard/orchestrator content', () => {
|
|
89
|
+
expect(content).not.toContain('## RCASD-IVTR+C');
|
|
85
90
|
expect(content).not.toContain('ORC-001');
|
|
86
|
-
expect(content).not.toContain('## ORC Constraints');
|
|
87
|
-
});
|
|
88
|
-
it('does not contain BASE constraints', () => {
|
|
89
|
-
expect(content).not.toContain('BASE-001');
|
|
90
|
-
expect(content).not.toContain('## BASE Constraints');
|
|
91
|
-
});
|
|
92
|
-
it('does not contain Spawn Pipeline section', () => {
|
|
93
91
|
expect(content).not.toContain('## Spawn Pipeline');
|
|
94
92
|
});
|
|
95
|
-
it('does not contain Architecture Overview section', () => {
|
|
96
|
-
expect(content).not.toContain('## Architecture Overview');
|
|
97
|
-
});
|
|
98
|
-
it('does not contain Token Pre-Resolution section', () => {
|
|
99
|
-
expect(content).not.toContain('## Token Pre-Resolution');
|
|
100
|
-
});
|
|
101
|
-
it('does not contain Subagent Lifecycle section', () => {
|
|
102
|
-
expect(content).not.toContain('## Subagent Lifecycle');
|
|
103
|
-
});
|
|
104
|
-
});
|
|
105
|
-
describe('Does NOT contain TIER markers', () => {
|
|
106
|
-
it('has no TIER opening markers', () => {
|
|
107
|
-
expect(content).not.toMatch(/<!-- TIER:\w+ -->/);
|
|
108
|
-
});
|
|
109
|
-
it('has no TIER closing markers', () => {
|
|
110
|
-
expect(content).not.toMatch(/<!-- \/TIER:\w+ -->/);
|
|
111
|
-
});
|
|
112
|
-
it('does not contain MVI Progressive Disclosure comment', () => {
|
|
113
|
-
expect(content).not.toContain('MVI Progressive Disclosure');
|
|
114
|
-
});
|
|
115
93
|
});
|
|
116
94
|
describe('Contains escalation section', () => {
|
|
117
|
-
it('has Escalation section
|
|
95
|
+
it('has Escalation section', () => {
|
|
118
96
|
expect(content).toContain('## Escalation');
|
|
119
97
|
});
|
|
120
98
|
it('points to ct-cleo skill', () => {
|
|
@@ -123,29 +101,15 @@ describe('CLEO-INJECTION v2.3.0 minimal-only template', () => {
|
|
|
123
101
|
it('points to ct-orchestrator skill', () => {
|
|
124
102
|
expect(content).toContain('ct-orchestrator');
|
|
125
103
|
});
|
|
126
|
-
it('points to admin help', () => {
|
|
127
|
-
expect(content).toContain('admin help');
|
|
128
|
-
});
|
|
129
|
-
it('points to operations reference', () => {
|
|
130
|
-
expect(content).toContain('CLEO-OPERATIONS-REFERENCE.md');
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
describe('References section', () => {
|
|
134
|
-
it('has References section', () => {
|
|
135
|
-
expect(content).toContain('## References');
|
|
136
|
-
});
|
|
137
|
-
it('references VERB-STANDARDS.md', () => {
|
|
138
|
-
expect(content).toContain('VERB-STANDARDS.md');
|
|
139
|
-
});
|
|
140
104
|
});
|
|
141
105
|
describe('Template size', () => {
|
|
142
|
-
it('is under
|
|
106
|
+
it('is under 100 lines (token-optimized)', () => {
|
|
143
107
|
const lines = content.split('\n').length;
|
|
144
|
-
expect(lines).
|
|
108
|
+
expect(lines).toBeLessThanOrEqual(100);
|
|
145
109
|
});
|
|
146
|
-
it('is at least
|
|
110
|
+
it('is at least 50 lines (not accidentally empty)', () => {
|
|
147
111
|
const lines = content.split('\n').length;
|
|
148
|
-
expect(lines).toBeGreaterThan(
|
|
112
|
+
expect(lines).toBeGreaterThan(50);
|
|
149
113
|
});
|
|
150
114
|
});
|
|
151
115
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"injection-mvi-tiers.test.js","sourceRoot":"","sources":["injection-mvi-tiers.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAChD
|
|
1
|
+
{"version":3,"file":"injection-mvi-tiers.test.js","sourceRoot":"","sources":["injection-mvi-tiers.test.ts"],"names":[],"mappings":"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"}
|
package/src/discovery.ts
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Greenfield/Brownfield project discovery & classification (Phase 5).
|
|
3
|
+
*
|
|
4
|
+
* Classifies a project directory as either:
|
|
5
|
+
* - **greenfield**: empty or nearly-empty, no code, no git history
|
|
6
|
+
* - **brownfield**: existing codebase with files, possibly git history
|
|
7
|
+
*
|
|
8
|
+
* Used by `cleo init` (Phase 5 bootstrap) to choose the correct seed
|
|
9
|
+
* pipeline:
|
|
10
|
+
* - Greenfield → seed a Vision/PRD "research" epic so the agent starts
|
|
11
|
+
* from a blank-slate planning posture
|
|
12
|
+
* - Brownfield → invoke codebase mapping and anchor findings in BRAIN as
|
|
13
|
+
* baseline context (per ULTRAPLAN §1 "Context Anchoring")
|
|
14
|
+
*
|
|
15
|
+
* Classification is intentionally simple and transparent — no heuristics,
|
|
16
|
+
* no ML, just file/directory presence checks. A project qualifies as
|
|
17
|
+
* brownfield if ANY of these are true:
|
|
18
|
+
* - `.git/` directory exists (under a size threshold we still treat as
|
|
19
|
+
* brownfield since history matters)
|
|
20
|
+
* - source files exist under common source dirs (src, lib, app, packages)
|
|
21
|
+
* - any manifest file exists (package.json, Cargo.toml, go.mod, pyproject.toml)
|
|
22
|
+
* - markdown docs already exist (README.md, docs/)
|
|
23
|
+
*
|
|
24
|
+
* @task Phase 5 — Greenfield/brownfield bootstrap + context anchoring
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
28
|
+
import { join, resolve } from 'node:path';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Classification result for a project directory.
|
|
32
|
+
*/
|
|
33
|
+
export interface ProjectClassification {
|
|
34
|
+
/** Project type: 'greenfield' (empty/new) or 'brownfield' (existing). */
|
|
35
|
+
kind: 'greenfield' | 'brownfield';
|
|
36
|
+
/** Absolute path that was classified. */
|
|
37
|
+
directory: string;
|
|
38
|
+
/** Signal list — which indicators led to the classification. */
|
|
39
|
+
signals: ClassificationSignal[];
|
|
40
|
+
/** Total non-hidden files found at the top level (for reporting). */
|
|
41
|
+
topLevelFileCount: number;
|
|
42
|
+
/** Whether a `.git/` directory is present. */
|
|
43
|
+
hasGit: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** A single classification signal detected on the filesystem. */
|
|
47
|
+
export interface ClassificationSignal {
|
|
48
|
+
/** Canonical signal id for programmatic handling. */
|
|
49
|
+
id:
|
|
50
|
+
| 'git-dir'
|
|
51
|
+
| 'source-dir'
|
|
52
|
+
| 'package-manifest'
|
|
53
|
+
| 'docs'
|
|
54
|
+
| 'rust-manifest'
|
|
55
|
+
| 'go-manifest'
|
|
56
|
+
| 'python-manifest'
|
|
57
|
+
| 'readme'
|
|
58
|
+
| 'empty';
|
|
59
|
+
/** Human-readable description of what was detected. */
|
|
60
|
+
description: string;
|
|
61
|
+
/** File or directory that triggered this signal. */
|
|
62
|
+
path: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ============================================================================
|
|
66
|
+
// Detection helpers
|
|
67
|
+
// ============================================================================
|
|
68
|
+
|
|
69
|
+
/** Common source directory names that indicate an existing codebase. */
|
|
70
|
+
const SOURCE_DIRS = ['src', 'lib', 'app', 'packages', 'crates', 'cmd', 'internal'];
|
|
71
|
+
|
|
72
|
+
/** Manifest files that indicate a managed project. */
|
|
73
|
+
const MANIFESTS: Array<{
|
|
74
|
+
file: string;
|
|
75
|
+
signal: ClassificationSignal['id'];
|
|
76
|
+
description: string;
|
|
77
|
+
}> = [
|
|
78
|
+
{ file: 'package.json', signal: 'package-manifest', description: 'Node.js manifest' },
|
|
79
|
+
{ file: 'Cargo.toml', signal: 'rust-manifest', description: 'Rust manifest' },
|
|
80
|
+
{ file: 'go.mod', signal: 'go-manifest', description: 'Go manifest' },
|
|
81
|
+
{ file: 'pyproject.toml', signal: 'python-manifest', description: 'Python manifest (PEP 621)' },
|
|
82
|
+
{ file: 'requirements.txt', signal: 'python-manifest', description: 'Python requirements.txt' },
|
|
83
|
+
{ file: 'setup.py', signal: 'python-manifest', description: 'Python setup.py' },
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
/** Docs files/directories that indicate prior work. */
|
|
87
|
+
const DOCS_MARKERS: Array<{ path: string; signal: ClassificationSignal['id'] }> = [
|
|
88
|
+
{ path: 'README.md', signal: 'readme' },
|
|
89
|
+
{ path: 'README.rst', signal: 'readme' },
|
|
90
|
+
{ path: 'docs', signal: 'docs' },
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Check if a directory (at the top level only) contains any files that are
|
|
95
|
+
* NOT part of the CLEO scaffolding itself. Used to decide whether the caller
|
|
96
|
+
* is about to `cleo init` into an already-populated directory.
|
|
97
|
+
*/
|
|
98
|
+
function countMeaningfulTopLevelFiles(directory: string): number {
|
|
99
|
+
if (!existsSync(directory)) return 0;
|
|
100
|
+
try {
|
|
101
|
+
const entries = readdirSync(directory);
|
|
102
|
+
let count = 0;
|
|
103
|
+
for (const entry of entries) {
|
|
104
|
+
// Ignore hidden files, the CLEO dir itself, and common CI/git debris
|
|
105
|
+
if (
|
|
106
|
+
entry.startsWith('.') ||
|
|
107
|
+
entry === '.cleo' ||
|
|
108
|
+
entry === 'node_modules' ||
|
|
109
|
+
entry === 'target' ||
|
|
110
|
+
entry === 'dist' ||
|
|
111
|
+
entry === 'build'
|
|
112
|
+
) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
count += 1;
|
|
116
|
+
}
|
|
117
|
+
return count;
|
|
118
|
+
} catch {
|
|
119
|
+
return 0;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ============================================================================
|
|
124
|
+
// Public API
|
|
125
|
+
// ============================================================================
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Classify a project directory as greenfield or brownfield.
|
|
129
|
+
*
|
|
130
|
+
* Read-only — never mutates the filesystem. Safe to call at any time.
|
|
131
|
+
*
|
|
132
|
+
* @param directory - Absolute or relative path; defaults to process.cwd()
|
|
133
|
+
* @returns Classification result with signals and metadata
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```typescript
|
|
137
|
+
* const classification = classifyProject('/my/project');
|
|
138
|
+
* if (classification.kind === 'greenfield') {
|
|
139
|
+
* // Seed initial Vision epic
|
|
140
|
+
* } else {
|
|
141
|
+
* // Run codebase mapping
|
|
142
|
+
* }
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
export function classifyProject(directory?: string): ProjectClassification {
|
|
146
|
+
const root = resolve(directory ?? process.cwd());
|
|
147
|
+
const signals: ClassificationSignal[] = [];
|
|
148
|
+
|
|
149
|
+
// Signal 1: .git directory
|
|
150
|
+
const gitPath = join(root, '.git');
|
|
151
|
+
const hasGit = existsSync(gitPath);
|
|
152
|
+
if (hasGit) {
|
|
153
|
+
signals.push({
|
|
154
|
+
id: 'git-dir',
|
|
155
|
+
description: '.git/ directory present — project has version history',
|
|
156
|
+
path: gitPath,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Signal 2: manifest files
|
|
161
|
+
for (const manifest of MANIFESTS) {
|
|
162
|
+
const manifestPath = join(root, manifest.file);
|
|
163
|
+
if (existsSync(manifestPath)) {
|
|
164
|
+
signals.push({
|
|
165
|
+
id: manifest.signal,
|
|
166
|
+
description: manifest.description,
|
|
167
|
+
path: manifestPath,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Signal 3: common source directories
|
|
173
|
+
for (const dir of SOURCE_DIRS) {
|
|
174
|
+
const srcPath = join(root, dir);
|
|
175
|
+
if (existsSync(srcPath) && isNonEmptyDir(srcPath)) {
|
|
176
|
+
signals.push({
|
|
177
|
+
id: 'source-dir',
|
|
178
|
+
description: `Source directory ${dir}/ contains files`,
|
|
179
|
+
path: srcPath,
|
|
180
|
+
});
|
|
181
|
+
break; // One source-dir signal is enough
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Signal 4: docs / README
|
|
186
|
+
for (const marker of DOCS_MARKERS) {
|
|
187
|
+
const markerPath = join(root, marker.path);
|
|
188
|
+
if (existsSync(markerPath)) {
|
|
189
|
+
signals.push({
|
|
190
|
+
id: marker.signal,
|
|
191
|
+
description: `${marker.path} present`,
|
|
192
|
+
path: markerPath,
|
|
193
|
+
});
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const topLevelFileCount = countMeaningfulTopLevelFiles(root);
|
|
199
|
+
|
|
200
|
+
// Empty signal if we have nothing
|
|
201
|
+
if (signals.length === 0 && topLevelFileCount === 0) {
|
|
202
|
+
signals.push({
|
|
203
|
+
id: 'empty',
|
|
204
|
+
description: 'No source files, manifests, git history, or docs detected',
|
|
205
|
+
path: root,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Classification rule: brownfield if ANY non-'empty' signal present
|
|
210
|
+
const kind: ProjectClassification['kind'] = signals.some((s) => s.id !== 'empty')
|
|
211
|
+
? 'brownfield'
|
|
212
|
+
: 'greenfield';
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
kind,
|
|
216
|
+
directory: root,
|
|
217
|
+
signals,
|
|
218
|
+
topLevelFileCount,
|
|
219
|
+
hasGit,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* True if a directory exists AND contains at least one non-hidden entry.
|
|
225
|
+
*/
|
|
226
|
+
function isNonEmptyDir(dirPath: string): boolean {
|
|
227
|
+
try {
|
|
228
|
+
const stat = statSync(dirPath);
|
|
229
|
+
if (!stat.isDirectory()) return false;
|
|
230
|
+
const entries = readdirSync(dirPath).filter((e) => !e.startsWith('.'));
|
|
231
|
+
return entries.length > 0;
|
|
232
|
+
} catch {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
@@ -64,7 +64,9 @@ const TIMESTAMP = '2026-03-24T00:00:00.000Z';
|
|
|
64
64
|
// ---------------------------------------------------------------------------
|
|
65
65
|
describe('hook automation E2E', () => {
|
|
66
66
|
beforeEach(() => {
|
|
67
|
-
observeBrainMock
|
|
67
|
+
observeBrainMock
|
|
68
|
+
.mockReset()
|
|
69
|
+
.mockResolvedValue({ id: 'O-test', type: 'observation', createdAt: '2026-01-01 00:00:00' });
|
|
68
70
|
loadConfigMock.mockReset().mockResolvedValue(makeConfig());
|
|
69
71
|
maybeRefreshMemoryBridgeMock.mockReset().mockResolvedValue(undefined);
|
|
70
72
|
// Clear work-capture env var
|