@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.
Files changed (144) hide show
  1. package/dist/discovery.d.ts +69 -0
  2. package/dist/discovery.d.ts.map +1 -0
  3. package/dist/index.d.ts +3 -2
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +1643 -2349
  6. package/dist/index.js.map +4 -4
  7. package/dist/init.d.ts +51 -0
  8. package/dist/init.d.ts.map +1 -1
  9. package/dist/internal.d.ts +9 -1
  10. package/dist/internal.d.ts.map +1 -1
  11. package/dist/lifecycle/default-chain.d.ts +8 -2
  12. package/dist/lifecycle/default-chain.d.ts.map +1 -1
  13. package/dist/lifecycle/index.d.ts +1 -0
  14. package/dist/lifecycle/index.d.ts.map +1 -1
  15. package/dist/lifecycle/stage-guidance.d.ts +140 -0
  16. package/dist/lifecycle/stage-guidance.d.ts.map +1 -0
  17. package/dist/orchestration/protocol-validators.d.ts +122 -3
  18. package/dist/orchestration/protocol-validators.d.ts.map +1 -1
  19. package/dist/paths.d.ts +91 -0
  20. package/dist/paths.d.ts.map +1 -1
  21. package/dist/scaffold.d.ts +31 -1
  22. package/dist/scaffold.d.ts.map +1 -1
  23. package/dist/skills/dispatch.d.ts +1 -1
  24. package/dist/skills/skill-paths.d.ts +9 -6
  25. package/dist/skills/skill-paths.d.ts.map +1 -1
  26. package/dist/validation/protocols/_shared.d.ts +40 -0
  27. package/dist/validation/protocols/_shared.d.ts.map +1 -0
  28. package/dist/validation/protocols/architecture-decision.d.ts +23 -0
  29. package/dist/validation/protocols/architecture-decision.d.ts.map +1 -0
  30. package/dist/validation/protocols/artifact-publish.d.ts +22 -0
  31. package/dist/validation/protocols/artifact-publish.d.ts.map +1 -0
  32. package/dist/validation/protocols/consensus.d.ts +11 -17
  33. package/dist/validation/protocols/consensus.d.ts.map +1 -1
  34. package/dist/validation/protocols/contribution.d.ts +12 -17
  35. package/dist/validation/protocols/contribution.d.ts.map +1 -1
  36. package/dist/validation/protocols/decomposition.d.ts +18 -21
  37. package/dist/validation/protocols/decomposition.d.ts.map +1 -1
  38. package/dist/validation/protocols/implementation.d.ts +9 -17
  39. package/dist/validation/protocols/implementation.d.ts.map +1 -1
  40. package/dist/validation/protocols/provenance.d.ts +23 -0
  41. package/dist/validation/protocols/provenance.d.ts.map +1 -0
  42. package/dist/validation/protocols/release.d.ts +25 -0
  43. package/dist/validation/protocols/release.d.ts.map +1 -0
  44. package/dist/validation/protocols/research.d.ts +9 -17
  45. package/dist/validation/protocols/research.d.ts.map +1 -1
  46. package/dist/validation/protocols/specification.d.ts +7 -17
  47. package/dist/validation/protocols/specification.d.ts.map +1 -1
  48. package/dist/validation/protocols/testing.d.ts +22 -0
  49. package/dist/validation/protocols/testing.d.ts.map +1 -0
  50. package/dist/validation/protocols/validation.d.ts +22 -0
  51. package/dist/validation/protocols/validation.d.ts.map +1 -0
  52. package/package.json +7 -7
  53. package/src/__tests__/injection-mvi-tiers.test.js +54 -90
  54. package/src/__tests__/injection-mvi-tiers.test.js.map +1 -1
  55. package/src/discovery.ts +235 -0
  56. package/src/hooks/handlers/__tests__/hook-automation-e2e.test.js +3 -1
  57. package/src/hooks/handlers/__tests__/hook-automation-e2e.test.js.map +1 -1
  58. package/src/index.ts +16 -0
  59. package/src/init.ts +196 -0
  60. package/src/internal.ts +31 -1
  61. package/src/lifecycle/default-chain.ts +11 -2
  62. package/src/lifecycle/index.ts +10 -0
  63. package/src/lifecycle/stage-guidance.ts +282 -0
  64. package/src/metrics/__tests__/provider-detection.test.js +19 -7
  65. package/src/metrics/__tests__/provider-detection.test.js.map +1 -1
  66. package/src/orchestration/__tests__/protocol-validators.test.js +228 -8
  67. package/src/orchestration/__tests__/protocol-validators.test.js.map +1 -1
  68. package/src/orchestration/__tests__/protocol-validators.test.ts +259 -7
  69. package/src/orchestration/protocol-validators.ts +419 -4
  70. package/src/paths.ts +110 -0
  71. package/src/scaffold.ts +240 -4
  72. package/src/skills/dispatch.ts +6 -6
  73. package/src/skills/skill-paths.ts +27 -23
  74. package/src/validation/protocols/_shared.ts +88 -0
  75. package/src/validation/protocols/architecture-decision.ts +52 -0
  76. package/src/validation/protocols/artifact-publish.ts +49 -0
  77. package/src/validation/protocols/consensus.ts +44 -74
  78. package/src/validation/protocols/contribution.ts +28 -65
  79. package/src/validation/protocols/decomposition.ts +37 -64
  80. package/src/validation/protocols/implementation.ts +25 -65
  81. package/src/validation/protocols/protocols-markdown/architecture-decision.md +303 -0
  82. package/src/validation/protocols/protocols-markdown/artifact-publish.md +600 -0
  83. package/src/validation/protocols/protocols-markdown/consensus.md +322 -0
  84. package/src/validation/protocols/protocols-markdown/contribution.md +388 -0
  85. package/src/validation/protocols/protocols-markdown/decomposition.md +421 -0
  86. package/src/validation/protocols/protocols-markdown/implementation.md +357 -0
  87. package/src/validation/protocols/protocols-markdown/provenance.md +613 -0
  88. package/src/validation/protocols/protocols-markdown/release.md +783 -0
  89. package/src/validation/protocols/protocols-markdown/research.md +261 -0
  90. package/src/validation/protocols/protocols-markdown/specification.md +300 -0
  91. package/src/validation/protocols/protocols-markdown/testing.md +287 -0
  92. package/src/validation/protocols/protocols-markdown/validation.md +242 -0
  93. package/src/validation/protocols/provenance.ts +50 -0
  94. package/src/validation/protocols/release.ts +44 -0
  95. package/src/validation/protocols/research.ts +25 -87
  96. package/src/validation/protocols/specification.ts +27 -89
  97. package/src/validation/protocols/testing.ts +46 -0
  98. package/src/validation/protocols/validation.ts +46 -0
  99. package/dist/validation/protocols/release-protocol.d.ts +0 -27
  100. package/dist/validation/protocols/release-protocol.d.ts.map +0 -1
  101. package/dist/validation/protocols/testing-protocol.d.ts +0 -27
  102. package/dist/validation/protocols/testing-protocol.d.ts.map +0 -1
  103. package/dist/validation/protocols/validation-protocol.d.ts +0 -27
  104. package/dist/validation/protocols/validation-protocol.d.ts.map +0 -1
  105. package/schemas/agent-configs.schema.json +0 -120
  106. package/schemas/agent-registry.schema.json +0 -132
  107. package/schemas/archive.schema.json +0 -450
  108. package/schemas/brain-decision.schema.json +0 -69
  109. package/schemas/brain-learning.schema.json +0 -57
  110. package/schemas/brain-pattern.schema.json +0 -72
  111. package/schemas/critical-path.schema.json +0 -246
  112. package/schemas/deps-cache.schema.json +0 -97
  113. package/schemas/doctor-output.schema.json +0 -283
  114. package/schemas/error.schema.json +0 -161
  115. package/schemas/global-config.schema.json +0 -219
  116. package/schemas/grade.schema.json +0 -49
  117. package/schemas/log.schema.json +0 -250
  118. package/schemas/metrics.schema.json +0 -328
  119. package/schemas/migrations.schema.json +0 -150
  120. package/schemas/nexus-registry.schema.json +0 -90
  121. package/schemas/operation-constitution.schema.json +0 -438
  122. package/schemas/output.schema.json +0 -164
  123. package/schemas/projects-registry.schema.json +0 -107
  124. package/schemas/protocol-frontmatter.schema.json +0 -72
  125. package/schemas/rcasd-consensus-report.schema.json +0 -10
  126. package/schemas/rcasd-evidence.schema.json +0 -42
  127. package/schemas/rcasd-gate-result.schema.json +0 -46
  128. package/schemas/rcasd-hitl-resolution.schema.json +0 -10
  129. package/schemas/rcasd-index.schema.json +0 -10
  130. package/schemas/rcasd-manifest.schema.json +0 -10
  131. package/schemas/rcasd-research-output.schema.json +0 -10
  132. package/schemas/rcasd-spec-frontmatter.schema.json +0 -10
  133. package/schemas/rcasd-stage-transition.schema.json +0 -38
  134. package/schemas/releases.schema.json +0 -267
  135. package/schemas/skills-manifest.schema.json +0 -91
  136. package/schemas/spec-index.schema.json +0 -196
  137. package/schemas/system-flow-atlas.schema.json +0 -125
  138. package/src/conduit/__tests__/dual-api-e2e.test.d.ts.map +0 -1
  139. package/src/conduit/__tests__/dual-api-e2e.test.js +0 -178
  140. package/src/conduit/__tests__/dual-api-e2e.test.js.map +0 -1
  141. package/src/conduit/__tests__/dual-api-e2e.test.ts +0 -212
  142. package/src/validation/protocols/release-protocol.ts +0 -80
  143. package/src/validation/protocols/testing-protocol.ts +0 -93
  144. package/src/validation/protocols/validation-protocol.ts +0 -93
@@ -41,14 +41,29 @@ export interface ManifestEntryInput {
41
41
  sources?: string[];
42
42
  }
43
43
 
44
- /** All supported protocol types. */
44
+ /**
45
+ * All supported protocol types.
46
+ *
47
+ * The canonical set covers all 9 RCASD-IVTR pipeline stages plus the 3
48
+ * cross-cutting protocols that compose with specific stages:
49
+ *
50
+ * - Pipeline stages: research, consensus, architecture-decision,
51
+ * specification, decomposition, implementation, validation, testing, release
52
+ * - Cross-cutting: contribution (at implementation),
53
+ * artifact-publish (at release), provenance (at release)
54
+ *
55
+ * @task T260 — unify pipeline stages and cross-cutting protocols
56
+ */
45
57
  export const PROTOCOL_TYPES = [
46
58
  'research',
47
59
  'consensus',
60
+ 'architecture-decision',
48
61
  'specification',
49
62
  'decomposition',
50
63
  'implementation',
51
64
  'contribution',
65
+ 'validation',
66
+ 'testing',
52
67
  'release',
53
68
  'artifact-publish',
54
69
  'provenance',
@@ -56,7 +71,17 @@ export const PROTOCOL_TYPES = [
56
71
 
57
72
  export type ProtocolType = (typeof PROTOCOL_TYPES)[number];
58
73
 
59
- /** Map protocol types to exit codes. */
74
+ /**
75
+ * Map protocol types to exit codes.
76
+ *
77
+ * Pipeline protocols use the 60-67 orchestrator range. Cross-cutting
78
+ * protocols with dedicated ranges (artifact-publish 85-89, provenance 90-94)
79
+ * use their own codes. Architecture-decision uses 84 PROVENANCE_REQUIRED
80
+ * because every ADR MUST be generated from an accepted Consensus verdict
81
+ * (ADR-001) — the provenance chain is the whole point.
82
+ *
83
+ * @task T260
84
+ */
60
85
  export const PROTOCOL_EXIT_CODES: Record<ProtocolType, ExitCode> = {
61
86
  research: ExitCode.PROTOCOL_MISSING, // 60
62
87
  consensus: ExitCode.INVALID_RETURN_MESSAGE, // 61
@@ -65,8 +90,11 @@ export const PROTOCOL_EXIT_CODES: Record<ProtocolType, ExitCode> = {
65
90
  implementation: ExitCode.AUTONOMOUS_BOUNDARY, // 64
66
91
  contribution: ExitCode.HANDOFF_REQUIRED, // 65
67
92
  release: ExitCode.RESUME_FAILED, // 66
68
- 'artifact-publish': ExitCode.CONCURRENT_SESSION, // 67
69
- provenance: ExitCode.CONCURRENT_SESSION, // 67 (shared with artifact-publish)
93
+ testing: ExitCode.CONCURRENT_SESSION, // 67 (shared: both testing and validation are lifecycle gates)
94
+ validation: ExitCode.LIFECYCLE_GATE_FAILED, // 80
95
+ 'architecture-decision': ExitCode.PROVENANCE_REQUIRED, // 84 (ADR-001: must link to consensus)
96
+ 'artifact-publish': ExitCode.ARTIFACT_PUBLISH_FAILED, // 88 (dedicated)
97
+ provenance: ExitCode.ATTESTATION_INVALID, // 94 (dedicated)
70
98
  };
71
99
 
72
100
  // ============================================================
@@ -615,6 +643,381 @@ export function validateProvenanceProtocol(
615
643
  return { valid: !hasErrors, protocol: 'provenance', violations, score: Math.max(0, score) };
616
644
  }
617
645
 
646
+ // ============================================================
647
+ // Architecture Decision Record Protocol (ADR-*)
648
+ // ============================================================
649
+
650
+ /**
651
+ * ADR lifecycle status values.
652
+ * @task T260
653
+ */
654
+ export type AdrStatus = 'proposed' | 'accepted' | 'superseded' | 'deprecated';
655
+
656
+ /** Architecture decision options for validator. */
657
+ export interface ArchitectureDecisionOptions {
658
+ /** Content of the ADR markdown document, used to verify required sections. */
659
+ adrContent?: string;
660
+ /** Current status of the decision record. */
661
+ status?: AdrStatus;
662
+ /** Whether a human-in-the-loop review has been completed (ADR-003). */
663
+ hitlReviewed?: boolean;
664
+ /** Whether downstream artifacts are flagged for review after supersession. */
665
+ downstreamFlagged?: boolean;
666
+ /** Whether the record is persisted in the canonical SQLite decisions table. */
667
+ persistedInDb?: boolean;
668
+ }
669
+
670
+ /**
671
+ * Validate an Architecture Decision Record manifest entry.
672
+ *
673
+ * Enforces the 8 MUST requirements from `architecture-decision.md`:
674
+ * ADR-001 (consensus provenance), ADR-002 (manifest link), ADR-003 (HITL),
675
+ * ADR-004 (required sections), ADR-005 (cascade on supersession),
676
+ * ADR-006 (SQLite persistence), ADR-007 (agent_type), ADR-008 (spec block).
677
+ *
678
+ * ADR-005, ADR-006, and ADR-008 require runtime state the caller must
679
+ * provide via options — this validator checks the options and never
680
+ * performs side-effectful I/O.
681
+ *
682
+ * @task T260
683
+ */
684
+ export function validateArchitectureDecisionProtocol(
685
+ entry: ManifestEntryInput & { consensus_manifest_id?: string },
686
+ options: ArchitectureDecisionOptions = {},
687
+ ): ProtocolValidationResult {
688
+ const violations: ProtocolViolation[] = [];
689
+ let score = 100;
690
+
691
+ // ADR-001 / ADR-002: MUST be generated from accepted Consensus and
692
+ // include a `consensus_manifest_id` link.
693
+ if (!entry.consensus_manifest_id || entry.consensus_manifest_id.trim().length === 0) {
694
+ violations.push({
695
+ requirement: 'ADR-001',
696
+ severity: 'error',
697
+ message: 'ADR must link to the originating consensus manifest',
698
+ fix: 'Add consensus_manifest_id to manifest entry referencing the accepted consensus',
699
+ });
700
+ score -= 25;
701
+ }
702
+
703
+ // ADR-003: Transition from proposed→accepted requires explicit HITL review.
704
+ if (options.status === 'accepted' && options.hitlReviewed === false) {
705
+ violations.push({
706
+ requirement: 'ADR-003',
707
+ severity: 'error',
708
+ message: 'ADR cannot be accepted without HITL (human-in-the-loop) review',
709
+ fix: 'Have a human review and approve the ADR before promoting to accepted',
710
+ });
711
+ score -= 30;
712
+ }
713
+
714
+ // ADR-004: MUST include Context, Options Evaluated, Decision, Rationale, Consequences.
715
+ if (options.adrContent) {
716
+ const requiredSections = [
717
+ /##\s+.*Context/i,
718
+ /##\s+.*Option/i,
719
+ /##\s+.*Decision/i,
720
+ /##\s+.*Rationale/i,
721
+ /##\s+.*Consequences/i,
722
+ ];
723
+ const missing = requiredSections.filter((re) => !re.test(options.adrContent!)).length;
724
+ if (missing > 0) {
725
+ violations.push({
726
+ requirement: 'ADR-004',
727
+ severity: 'error',
728
+ message: `ADR missing ${missing} of 5 required sections (Context, Options, Decision, Rationale, Consequences)`,
729
+ fix: 'Add all five canonical sections to the ADR body',
730
+ });
731
+ score -= 20;
732
+ }
733
+ }
734
+
735
+ // ADR-005: If the record is superseded, downstream artifacts MUST be flagged.
736
+ if (options.status === 'superseded' && options.downstreamFlagged === false) {
737
+ violations.push({
738
+ requirement: 'ADR-005',
739
+ severity: 'error',
740
+ message: 'Superseded ADR has not triggered downstream invalidation cascade',
741
+ fix: 'Flag linked specifications, decomposition, and implementations for review',
742
+ });
743
+ score -= 20;
744
+ }
745
+
746
+ // ADR-006: MUST be persisted in the canonical SQLite decisions table.
747
+ if (options.persistedInDb === false) {
748
+ violations.push({
749
+ requirement: 'ADR-006',
750
+ severity: 'error',
751
+ message: 'ADR not persisted in canonical decisions SQLite table',
752
+ fix: 'Insert the decision via the Drizzle ORM architectureDecisions table',
753
+ });
754
+ score -= 15;
755
+ }
756
+
757
+ // ADR-007: MUST set agent_type: decision
758
+ if (!checkAgentType(entry, 'decision')) {
759
+ violations.push({
760
+ requirement: 'ADR-007',
761
+ severity: 'error',
762
+ message: `agent_type must be decision, got ${entry.agent_type ?? 'undefined'}`,
763
+ fix: 'Update manifest entry agent_type field to "decision"',
764
+ });
765
+ score -= 15;
766
+ }
767
+
768
+ // ADR (output file): MUST produce an ADR markdown document.
769
+ if (!checkRequiredField(entry, 'file')) {
770
+ violations.push({
771
+ requirement: 'ADR-004',
772
+ severity: 'error',
773
+ message: 'ADR must produce an output markdown file',
774
+ fix: 'Write the ADR to disk and reference it in the manifest entry',
775
+ });
776
+ score -= 10;
777
+ }
778
+
779
+ const hasErrors = violations.some((v) => v.severity === 'error');
780
+ return {
781
+ valid: !hasErrors,
782
+ protocol: 'architecture-decision',
783
+ violations,
784
+ score: Math.max(0, score),
785
+ };
786
+ }
787
+
788
+ // ============================================================
789
+ // Validation Protocol (VALID-*)
790
+ // ============================================================
791
+
792
+ /** Validation-stage options for validator. */
793
+ export interface ValidationStageOptions {
794
+ /** Whether static analysis / type check passed (VALID-001). */
795
+ specMatchConfirmed?: boolean;
796
+ /** Whether the existing test suite ran successfully (VALID-002). */
797
+ testSuitePassed?: boolean;
798
+ /** Whether upstream protocol compliance checks passed (VALID-003). */
799
+ protocolComplianceChecked?: boolean;
800
+ }
801
+
802
+ /**
803
+ * Validate a manifest entry against the validation stage protocol.
804
+ *
805
+ * Enforces VALID-001..007 from `validation.md`. The validation stage runs
806
+ * static analysis, type checking, and pre-test quality gates. This validator
807
+ * verifies the manifest entry captures a real validation run; runtime gate
808
+ * enforcement happens in the lifecycle state machine, not here.
809
+ *
810
+ * @task T260
811
+ */
812
+ export function validateValidationProtocol(
813
+ entry: ManifestEntryInput,
814
+ options: ValidationStageOptions = {},
815
+ ): ProtocolValidationResult {
816
+ const violations: ProtocolViolation[] = [];
817
+ let score = 100;
818
+
819
+ // VALID-001: MUST verify implementation matches specification
820
+ if (options.specMatchConfirmed === false) {
821
+ violations.push({
822
+ requirement: 'VALID-001',
823
+ severity: 'error',
824
+ message: 'Validation must confirm implementation matches specification',
825
+ fix: 'Run spec-match validation before reporting completion',
826
+ });
827
+ score -= 25;
828
+ }
829
+
830
+ // VALID-002: MUST run existing test suite and report results
831
+ if (options.testSuitePassed === false) {
832
+ violations.push({
833
+ requirement: 'VALID-002',
834
+ severity: 'error',
835
+ message: 'Existing test suite failed during validation',
836
+ fix: 'Fix failing tests before completing the validation stage',
837
+ });
838
+ score -= 25;
839
+ }
840
+
841
+ // VALID-003: MUST check protocol compliance
842
+ if (options.protocolComplianceChecked === false) {
843
+ violations.push({
844
+ requirement: 'VALID-003',
845
+ severity: 'error',
846
+ message: 'Upstream protocol compliance not checked',
847
+ fix: 'Run cleo check protocol for every upstream protocol before validation exits',
848
+ });
849
+ score -= 20;
850
+ }
851
+
852
+ // VALID-005: MUST write validation summary (key_findings) to manifest
853
+ if (!checkArrayMinLength(entry, 'key_findings', 1)) {
854
+ violations.push({
855
+ requirement: 'VALID-005',
856
+ severity: 'error',
857
+ message: 'Validation must record summary findings in manifest entry',
858
+ fix: 'Populate key_findings with pass/fail counts and coverage',
859
+ });
860
+ score -= 15;
861
+ }
862
+
863
+ // VALID-006: MUST set agent_type: validation
864
+ if (!checkAgentType(entry, 'validation')) {
865
+ violations.push({
866
+ requirement: 'VALID-006',
867
+ severity: 'error',
868
+ message: `agent_type must be validation, got ${entry.agent_type ?? 'undefined'}`,
869
+ fix: 'Update manifest entry agent_type field to "validation"',
870
+ });
871
+ score -= 15;
872
+ }
873
+
874
+ const hasErrors = violations.some((v) => v.severity === 'error');
875
+ return { valid: !hasErrors, protocol: 'validation', violations, score: Math.max(0, score) };
876
+ }
877
+
878
+ // ============================================================
879
+ // Testing Protocol (TEST-*)
880
+ // ============================================================
881
+
882
+ /**
883
+ * Project-agnostic test framework identifiers.
884
+ *
885
+ * The testing protocol is deliberately framework-neutral. Whichever
886
+ * framework the project uses, the protocol only cares that tests run
887
+ * autonomously via a framework adapter and loop until the spec is met.
888
+ *
889
+ * @task T260
890
+ */
891
+ export type TestFramework =
892
+ | 'vitest'
893
+ | 'jest'
894
+ | 'mocha'
895
+ | 'pytest'
896
+ | 'unittest'
897
+ | 'go-test'
898
+ | 'cargo-test'
899
+ | 'rspec'
900
+ | 'phpunit'
901
+ | 'bats'
902
+ | 'other';
903
+
904
+ /** Testing-stage options for validator. */
905
+ export interface TestingOptions {
906
+ /** Detected or declared test framework for the current worktree. */
907
+ framework?: TestFramework;
908
+ /** Total number of tests executed. */
909
+ testsRun?: number;
910
+ /** Number of tests that passed. */
911
+ testsPassed?: number;
912
+ /** Number of tests that failed. */
913
+ testsFailed?: number;
914
+ /** Coverage percentage achieved (0-100). */
915
+ coveragePercent?: number;
916
+ /** Minimum coverage threshold from project config. */
917
+ coverageThreshold?: number;
918
+ /** Whether the implementation→validate→test loop converged (spec met). */
919
+ ivtLoopConverged?: boolean;
920
+ /** Number of IVT loop iterations until convergence. */
921
+ ivtLoopIterations?: number;
922
+ }
923
+
924
+ /**
925
+ * Validate a manifest entry against the testing protocol.
926
+ *
927
+ * This validator is **project-agnostic**: it makes no assumption about the
928
+ * underlying test framework. It enforces the invariant that tests ran via
929
+ * a detected framework, achieved 100% pass rate, and (if an IVT loop was
930
+ * used) converged before the stage completes.
931
+ *
932
+ * Enforces TEST-001..007 from the post-2026-04 rewrite of `testing.md`.
933
+ *
934
+ * @task T260
935
+ */
936
+ export function validateTestingProtocol(
937
+ entry: ManifestEntryInput,
938
+ options: TestingOptions = {},
939
+ ): ProtocolValidationResult {
940
+ const violations: ProtocolViolation[] = [];
941
+ let score = 100;
942
+
943
+ // TEST-001: MUST identify the detected test framework (project-agnostic).
944
+ if (!options.framework) {
945
+ violations.push({
946
+ requirement: 'TEST-001',
947
+ severity: 'error',
948
+ message: 'Test framework not identified for the current worktree',
949
+ fix: 'Detect or declare the project test framework before running tests',
950
+ });
951
+ score -= 20;
952
+ }
953
+
954
+ // TEST-004: MUST achieve 100% pass rate before release.
955
+ if (
956
+ options.testsRun !== undefined &&
957
+ options.testsFailed !== undefined &&
958
+ options.testsFailed > 0
959
+ ) {
960
+ violations.push({
961
+ requirement: 'TEST-004',
962
+ severity: 'error',
963
+ message: `${options.testsFailed} of ${options.testsRun} tests failed`,
964
+ fix: 'Fix failing tests; re-enter the IVT loop until all pass',
965
+ });
966
+ score -= 30;
967
+ }
968
+
969
+ // TEST-005: IVT loop MUST converge (spec satisfied) before testing exits.
970
+ if (options.ivtLoopConverged === false) {
971
+ violations.push({
972
+ requirement: 'TEST-005',
973
+ severity: 'error',
974
+ message: 'Implement→Validate→Test loop has not converged on specification',
975
+ fix: 'Continue IVT iterations until implementation satisfies the spec',
976
+ });
977
+ score -= 25;
978
+ }
979
+
980
+ // TEST-006: MUST include test summary (key_findings) in manifest.
981
+ if (!checkArrayMinLength(entry, 'key_findings', 1)) {
982
+ violations.push({
983
+ requirement: 'TEST-006',
984
+ severity: 'error',
985
+ message: 'Testing output must record pass/fail summary in key_findings',
986
+ fix: 'Populate key_findings with framework, pass count, fail count, coverage',
987
+ });
988
+ score -= 10;
989
+ }
990
+
991
+ // TEST-007: MUST set agent_type: testing
992
+ if (!checkAgentType(entry, 'testing')) {
993
+ violations.push({
994
+ requirement: 'TEST-007',
995
+ severity: 'error',
996
+ message: `agent_type must be testing, got ${entry.agent_type ?? 'undefined'}`,
997
+ fix: 'Update manifest entry agent_type field to "testing"',
998
+ });
999
+ score -= 15;
1000
+ }
1001
+
1002
+ // Coverage threshold (advisory, non-blocking unless explicit threshold given).
1003
+ if (
1004
+ options.coveragePercent !== undefined &&
1005
+ options.coverageThreshold !== undefined &&
1006
+ options.coveragePercent < options.coverageThreshold
1007
+ ) {
1008
+ violations.push({
1009
+ requirement: 'TEST-004',
1010
+ severity: 'warning',
1011
+ message: `Coverage ${options.coveragePercent}% below threshold ${options.coverageThreshold}%`,
1012
+ fix: 'Add tests for uncovered code paths',
1013
+ });
1014
+ score -= 5;
1015
+ }
1016
+
1017
+ const hasErrors = violations.some((v) => v.severity === 'error');
1018
+ return { valid: !hasErrors, protocol: 'testing', violations, score: Math.max(0, score) };
1019
+ }
1020
+
618
1021
  // ============================================================
619
1022
  // Unified Dispatcher
620
1023
  // ============================================================
@@ -680,6 +1083,18 @@ export function validateProtocol(
680
1083
  options as { hasAttestation?: boolean; hasSbom?: boolean },
681
1084
  );
682
1085
  break;
1086
+ case 'architecture-decision':
1087
+ result = validateArchitectureDecisionProtocol(
1088
+ entry as ManifestEntryInput & { consensus_manifest_id?: string },
1089
+ options as ArchitectureDecisionOptions,
1090
+ );
1091
+ break;
1092
+ case 'validation':
1093
+ result = validateValidationProtocol(entry, options as ValidationStageOptions);
1094
+ break;
1095
+ case 'testing':
1096
+ result = validateTestingProtocol(entry, options as TestingOptions);
1097
+ break;
683
1098
  default:
684
1099
  throw new CleoError(ExitCode.CONCURRENT_SESSION, `Unknown protocol: ${protocol as string}`);
685
1100
  }
package/src/paths.ts CHANGED
@@ -354,6 +354,116 @@ export function getGlobalConfigPath(): string {
354
354
  return join(getCleoHome(), 'config.json');
355
355
  }
356
356
 
357
+ // ============================================================================
358
+ // CleoOS Hub Paths (Phase 1)
359
+ // ============================================================================
360
+
361
+ /**
362
+ * Get the Global Justfile Hub directory.
363
+ *
364
+ * The hub stores cross-project recipe libraries agents can run in ANY project
365
+ * (cleo-bootstrap, rcasd-init, schema-validate, lint-standard). Both humans
366
+ * (via editor) and the meta Cleo Chef Agent write recipes here.
367
+ *
368
+ * @returns Absolute path to the global-recipes directory under CLEO home
369
+ *
370
+ * @remarks
371
+ * Returns `{cleoHome}/global-recipes`. Created by `ensureGlobalHome()`.
372
+ *
373
+ * @example
374
+ * ```typescript
375
+ * const dir = getCleoGlobalRecipesDir();
376
+ * // Linux: "/home/user/.local/share/cleo/global-recipes"
377
+ * ```
378
+ */
379
+ export function getCleoGlobalRecipesDir(): string {
380
+ return join(getCleoHome(), 'global-recipes');
381
+ }
382
+
383
+ /**
384
+ * Get the absolute path to the primary global justfile.
385
+ *
386
+ * @returns Absolute path to `{cleoHome}/global-recipes/justfile`
387
+ *
388
+ * @remarks
389
+ * This is the single-file entry point for the Justfile Hub. Additional
390
+ * domain-specific justfiles live alongside it in the same directory.
391
+ *
392
+ * @example
393
+ * ```typescript
394
+ * const path = getCleoGlobalJustfilePath();
395
+ * ```
396
+ */
397
+ export function getCleoGlobalJustfilePath(): string {
398
+ return join(getCleoGlobalRecipesDir(), 'justfile');
399
+ }
400
+
401
+ /**
402
+ * Get the Global Pi Extensions Hub directory.
403
+ *
404
+ * Houses the Pi extensions that drive the CleoOS UI and tools:
405
+ * orchestrator.ts (Conductor Loop), project-manager.ts (TUI dashboard),
406
+ * tilldone.ts (work visualization), cant-bridge.ts (CANT runtime),
407
+ * stage-guide.ts (before_agent_start hook).
408
+ *
409
+ * @returns Absolute path to the pi-extensions directory under CLEO home
410
+ *
411
+ * @remarks
412
+ * Returns `{cleoHome}/pi-extensions`. Pi is configured to load extensions
413
+ * from this directory via settings.json or the PI extension path setting.
414
+ *
415
+ * @example
416
+ * ```typescript
417
+ * const dir = getCleoPiExtensionsDir();
418
+ * // Linux: "/home/user/.local/share/cleo/pi-extensions"
419
+ * ```
420
+ */
421
+ export function getCleoPiExtensionsDir(): string {
422
+ return join(getCleoHome(), 'pi-extensions');
423
+ }
424
+
425
+ /**
426
+ * Get the Global CANT Workflows Hub directory.
427
+ *
428
+ * Stores compiled and parsed `.cant` workflows that agents can invoke
429
+ * globally across projects. Project-local agents still live in `.cleo/agents/`.
430
+ *
431
+ * @returns Absolute path to the cant-workflows directory under CLEO home
432
+ *
433
+ * @remarks
434
+ * Returns `{cleoHome}/cant-workflows`. Used by the CANT runtime bridge
435
+ * to resolve globally-available workflow definitions.
436
+ *
437
+ * @example
438
+ * ```typescript
439
+ * const dir = getCleoCantWorkflowsDir();
440
+ * ```
441
+ */
442
+ export function getCleoCantWorkflowsDir(): string {
443
+ return join(getCleoHome(), 'cant-workflows');
444
+ }
445
+
446
+ /**
447
+ * Get the Global CLEO Agents directory.
448
+ *
449
+ * Holds globally-available CANT agent definitions (`.cant` files).
450
+ * Project-local agents still live in `{projectRoot}/.cleo/agents/`.
451
+ *
452
+ * @returns Absolute path to the agents directory under CLEO home
453
+ *
454
+ * @remarks
455
+ * Returns `{cleoHome}/agents`. Loaded when `cleo agent start <id>` resolves
456
+ * agent IDs that aren't found in the project-local registry.
457
+ *
458
+ * @example
459
+ * ```typescript
460
+ * const dir = getCleoGlobalAgentsDir();
461
+ * ```
462
+ */
463
+ export function getCleoGlobalAgentsDir(): string {
464
+ return join(getCleoHome(), 'agents');
465
+ }
466
+
357
467
  // ============================================================================
358
468
  // Agent Outputs
359
469
  // ============================================================================