ultra-builder-pro-cli 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.ultra-template/changes/active/.gitkeep +1 -0
- package/.ultra-template/changes/archive/.gitkeep +1 -0
- package/CHANGELOG.md +62 -11
- package/README.md +26 -18
- package/adapters/_shared/handbook.cjs +7 -7
- package/adapters/_shared/runtime-assets.cjs +2 -0
- package/adapters/_shared/tests/handbook.test.cjs +4 -1
- package/adapters/_shared/tests/runtime-assets.test.cjs +2 -0
- package/adapters/opencode.js +81 -20
- package/adapters/tests/codex.test.cjs +5 -3
- package/adapters/tests/opencode.test.cjs +16 -9
- package/bin/install.js +0 -19
- package/bin/orchestrator.js +1 -26
- package/commands/ultra-change.md +47 -0
- package/commands/ultra-deliver.md +14 -7
- package/commands/ultra-dev.md +6 -3
- package/commands/ultra-doctor.md +36 -0
- package/commands/ultra-init.md +4 -3
- package/commands/ultra-status.md +7 -5
- package/commands/ultra-test.md +2 -1
- package/docs/AGENT-CONTEXT.md +31 -23
- package/docs/ARCHITECTURE.md +46 -17
- package/docs/PLAN.zh-CN.md +35 -29
- package/docs/ROADMAP.md +14 -14
- package/docs/RUNTIME-COMPAT-MATRIX.md +11 -11
- package/hooks/README.md +8 -7
- package/hooks/active_task_context.py +22 -15
- package/hooks/health_check.py +136 -38
- package/hooks/workflow_context.py +120 -30
- package/mcp-server/lib/change-workflow.cjs +556 -0
- package/mcp-server/lib/change-workflow.test.cjs +259 -0
- package/mcp-server/lib/doctor.cjs +112 -0
- package/mcp-server/lib/doctor.test.cjs +86 -0
- package/mcp-server/lib/init-project.cjs +32 -6
- package/mcp-server/lib/runtime-state.cjs +222 -0
- package/mcp-server/lib/runtime-state.test.cjs +92 -0
- package/mcp-server/lib/state-db.cjs +31 -8
- package/mcp-server/lib/state-ops.cjs +17 -3
- package/mcp-server/server.cjs +102 -10
- package/mcp-server/tests/db-ops.test.cjs +5 -1
- package/mcp-server/tests/init-project.test.cjs +45 -0
- package/mcp-server/tests/server-contract.test.cjs +123 -1
- package/mcp-server/tests/state-db.test.cjs +44 -7
- package/mcp-server/tests/state-ops.test.cjs +19 -0
- package/orchestrator/daemon.cjs +39 -0
- package/orchestrator/tests/daemon.test.cjs +50 -0
- package/package.json +3 -3
- package/settings.json +1 -2
- package/skills/ultra-change/SKILL.md +140 -0
- package/skills/ultra-deliver/SKILL.md +81 -15
- package/skills/ultra-dev/SKILL.md +32 -12
- package/skills/ultra-doctor/SKILL.md +90 -0
- package/skills/ultra-init/SKILL.md +8 -5
- package/skills/ultra-review/SKILL.md +16 -0
- package/skills/ultra-status/SKILL.md +30 -7
- package/skills/ultra-test/SKILL.md +14 -2
- package/spec/cli-protocol.md +12 -1
- package/spec/mcp-tools.yaml +337 -1
- package/spec/schemas/mcp-tools.schema.json +1 -1
- package/spec/schemas/state-db.sql +117 -0
- package/ultra-tools/cli.cjs +3 -0
- package/ultra-tools/commands/system.cjs +57 -0
- package/ultra-tools/system-command.test.cjs +53 -0
- package/adapters/_shared/rtk-detect.cjs +0 -86
- package/adapters/_shared/tests/rtk-detect.test.cjs +0 -116
- package/docs/LEGACY-HERMES.md +0 -1099
- package/orchestrator/code-graph-watcher.cjs +0 -121
- package/orchestrator/tests/code-graph-watcher.test.cjs +0 -183
package/spec/mcp-tools.yaml
CHANGED
|
@@ -23,6 +23,8 @@ families:
|
|
|
23
23
|
- { name: task, description: 'authoritative task lifecycle on state.db', phase: '2' }
|
|
24
24
|
- { name: session, description: 'execution-unit lifecycle with single-active-lease admission (D33)', phase: '4.5' }
|
|
25
25
|
- { name: plan, description: 'execution-plan artifact export and query (D34)', phase: '8a' }
|
|
26
|
+
- { name: change, description: 'continuous post-delivery change lifecycle and convergence', phase: '9' }
|
|
27
|
+
- { name: system, description: 'read-only diagnostics and explicit backup-first repair', phase: '9' }
|
|
26
28
|
|
|
27
29
|
tools:
|
|
28
30
|
|
|
@@ -48,6 +50,7 @@ tools:
|
|
|
48
50
|
parent_id: { type: string, description: 'Phase 8A.3 parent reference for task.expand children' }
|
|
49
51
|
tag: { type: string, description: 'git branch tag (Phase 7.2)' }
|
|
50
52
|
trace_to: { type: string, description: 'spec anchor (e.g. spec/mcp-tools.yaml#task.create)' }
|
|
53
|
+
change_id: { type: string, pattern: '^[a-zA-Z0-9_\-]+$', description: 'continuous change unit owning this task' }
|
|
51
54
|
output_schema:
|
|
52
55
|
type: object
|
|
53
56
|
required: [id, status, created_at]
|
|
@@ -90,6 +93,7 @@ tools:
|
|
|
90
93
|
stale: { type: boolean }
|
|
91
94
|
tag: { type: string }
|
|
92
95
|
completion_commit: { type: string, pattern: '^[0-9a-f]{7,40}$' }
|
|
96
|
+
change_id: { type: ['string', 'null'], pattern: '^[a-zA-Z0-9_\-]+$' }
|
|
93
97
|
output_schema:
|
|
94
98
|
type: object
|
|
95
99
|
required: [ok, task]
|
|
@@ -245,12 +249,15 @@ tools:
|
|
|
245
249
|
description: override path to the template root (defaults to bundled templates/.ultra/)
|
|
246
250
|
output_schema:
|
|
247
251
|
type: object
|
|
248
|
-
required: [created_path, status, copied_files]
|
|
252
|
+
required: [created_path, state_db_path, status, copied_files]
|
|
249
253
|
additionalProperties: false
|
|
250
254
|
properties:
|
|
251
255
|
created_path:
|
|
252
256
|
type: string
|
|
253
257
|
description: absolute path to the created `.ultra/` directory
|
|
258
|
+
state_db_path:
|
|
259
|
+
type: string
|
|
260
|
+
description: absolute path to the initialized authoritative SQLite database
|
|
254
261
|
status:
|
|
255
262
|
type: string
|
|
256
263
|
enum: [created, overwritten]
|
|
@@ -358,6 +365,7 @@ tools:
|
|
|
358
365
|
additionalProperties: false
|
|
359
366
|
properties:
|
|
360
367
|
task_id: { type: string }
|
|
368
|
+
change_id: { type: string }
|
|
361
369
|
session_id: { type: string }
|
|
362
370
|
runtime: { type: string, enum: [claude, opencode, codex] }
|
|
363
371
|
type:
|
|
@@ -385,6 +393,11 @@ tools:
|
|
|
385
393
|
- cost_alert
|
|
386
394
|
- token_alert
|
|
387
395
|
- circuit_open
|
|
396
|
+
- change_created
|
|
397
|
+
- change_updated
|
|
398
|
+
- change_blocked
|
|
399
|
+
- change_converged
|
|
400
|
+
- change_archived
|
|
388
401
|
payload: { type: object }
|
|
389
402
|
output_schema:
|
|
390
403
|
type: object
|
|
@@ -614,6 +627,329 @@ tools:
|
|
|
614
627
|
emits_events: []
|
|
615
628
|
trace_to: '§7.3, D31'
|
|
616
629
|
|
|
630
|
+
# ────────────────────────── change family ──────────────────────────────
|
|
631
|
+
- name: change.create
|
|
632
|
+
family: change
|
|
633
|
+
description: Create a continuous feature, fix, redesign, or incident change unit and its initial context manifest.
|
|
634
|
+
phase: '9'
|
|
635
|
+
writer_role: mcp
|
|
636
|
+
input_schema:
|
|
637
|
+
type: object
|
|
638
|
+
required: [id, title, kind, intent]
|
|
639
|
+
additionalProperties: false
|
|
640
|
+
properties:
|
|
641
|
+
id: { type: string, pattern: '^[a-zA-Z0-9_\-]+$' }
|
|
642
|
+
title: { type: string, minLength: 3 }
|
|
643
|
+
kind: { type: string, enum: [quick, standard, major, incident] }
|
|
644
|
+
intent: { type: string, minLength: 3 }
|
|
645
|
+
base_commit: { type: string, pattern: '^[0-9a-f]{7,40}$' }
|
|
646
|
+
docs_impact:
|
|
647
|
+
type: object
|
|
648
|
+
additionalProperties: false
|
|
649
|
+
properties:
|
|
650
|
+
status: { type: string, enum: [unknown, required, none] }
|
|
651
|
+
files: { type: array, items: { type: string } }
|
|
652
|
+
rationale: { type: ['string', 'null'] }
|
|
653
|
+
provider_refs:
|
|
654
|
+
type: object
|
|
655
|
+
additionalProperties: false
|
|
656
|
+
properties:
|
|
657
|
+
memory:
|
|
658
|
+
type: object
|
|
659
|
+
required: [provider]
|
|
660
|
+
additionalProperties: false
|
|
661
|
+
properties:
|
|
662
|
+
provider: { type: string }
|
|
663
|
+
project: { type: string }
|
|
664
|
+
reference: { type: string }
|
|
665
|
+
revision: { type: string }
|
|
666
|
+
indexed_head: { type: string }
|
|
667
|
+
status: { type: string, enum: [available, unavailable, fresh, stale, unknown] }
|
|
668
|
+
code_graph:
|
|
669
|
+
type: object
|
|
670
|
+
required: [provider]
|
|
671
|
+
additionalProperties: false
|
|
672
|
+
properties:
|
|
673
|
+
provider: { type: string }
|
|
674
|
+
project: { type: string }
|
|
675
|
+
reference: { type: string }
|
|
676
|
+
revision: { type: string }
|
|
677
|
+
indexed_head: { type: string }
|
|
678
|
+
status: { type: string, enum: [available, unavailable, fresh, stale, unknown] }
|
|
679
|
+
output_schema:
|
|
680
|
+
type: object
|
|
681
|
+
required: [change, intent_path, context_manifest_path]
|
|
682
|
+
additionalProperties: false
|
|
683
|
+
properties:
|
|
684
|
+
change: { type: object }
|
|
685
|
+
intent_path: { type: string }
|
|
686
|
+
context_manifest_path: { type: string }
|
|
687
|
+
errors:
|
|
688
|
+
- { code: DUPLICATE_CHANGE_ID, description: 'change id already exists' }
|
|
689
|
+
- { code: PROVIDER_CONTENT_FORBIDDEN, description: 'provider payload content must remain external' }
|
|
690
|
+
- { code: VALIDATION_ERROR, description: 'change input failed validation' }
|
|
691
|
+
cli_subcommand: change create
|
|
692
|
+
emits_events: [change_created, context_updated]
|
|
693
|
+
|
|
694
|
+
- name: change.update
|
|
695
|
+
family: change
|
|
696
|
+
description: Update change intent, documentation impact, external provider metadata, or active status.
|
|
697
|
+
phase: '9'
|
|
698
|
+
writer_role: mcp
|
|
699
|
+
input_schema:
|
|
700
|
+
type: object
|
|
701
|
+
required: [id, patch]
|
|
702
|
+
additionalProperties: false
|
|
703
|
+
properties:
|
|
704
|
+
id: { type: string }
|
|
705
|
+
patch:
|
|
706
|
+
type: object
|
|
707
|
+
additionalProperties: false
|
|
708
|
+
properties:
|
|
709
|
+
title: { type: string, minLength: 3 }
|
|
710
|
+
intent: { type: string, minLength: 3 }
|
|
711
|
+
status: { type: string, enum: [active, blocked, cancelled] }
|
|
712
|
+
docs_impact:
|
|
713
|
+
type: object
|
|
714
|
+
additionalProperties: false
|
|
715
|
+
properties:
|
|
716
|
+
status: { type: string, enum: [unknown, required, none] }
|
|
717
|
+
files: { type: array, items: { type: string } }
|
|
718
|
+
rationale: { type: ['string', 'null'] }
|
|
719
|
+
provider_refs:
|
|
720
|
+
type: object
|
|
721
|
+
additionalProperties: false
|
|
722
|
+
properties:
|
|
723
|
+
memory:
|
|
724
|
+
type: object
|
|
725
|
+
required: [provider]
|
|
726
|
+
additionalProperties: false
|
|
727
|
+
properties:
|
|
728
|
+
provider: { type: string }
|
|
729
|
+
project: { type: string }
|
|
730
|
+
reference: { type: string }
|
|
731
|
+
revision: { type: string }
|
|
732
|
+
indexed_head: { type: string }
|
|
733
|
+
status: { type: string, enum: [available, unavailable, fresh, stale, unknown] }
|
|
734
|
+
code_graph:
|
|
735
|
+
type: object
|
|
736
|
+
required: [provider]
|
|
737
|
+
additionalProperties: false
|
|
738
|
+
properties:
|
|
739
|
+
provider: { type: string }
|
|
740
|
+
project: { type: string }
|
|
741
|
+
reference: { type: string }
|
|
742
|
+
revision: { type: string }
|
|
743
|
+
indexed_head: { type: string }
|
|
744
|
+
status: { type: string, enum: [available, unavailable, fresh, stale, unknown] }
|
|
745
|
+
output_schema:
|
|
746
|
+
type: object
|
|
747
|
+
required: [ok, change]
|
|
748
|
+
additionalProperties: false
|
|
749
|
+
properties:
|
|
750
|
+
ok: { type: boolean }
|
|
751
|
+
change: { type: object }
|
|
752
|
+
errors:
|
|
753
|
+
- { code: CHANGE_NOT_FOUND, description: 'change id does not exist' }
|
|
754
|
+
- { code: CHANGE_NOT_MUTABLE, description: 'ready, archived, and cancelled changes are immutable' }
|
|
755
|
+
- { code: ILLEGAL_CHANGE_TRANSITION, description: 'change transition is not permitted' }
|
|
756
|
+
- { code: PROVIDER_CONTENT_FORBIDDEN, description: 'provider payload content must remain external' }
|
|
757
|
+
cli_subcommand: change update
|
|
758
|
+
emits_events: [change_updated]
|
|
759
|
+
|
|
760
|
+
- name: change.get
|
|
761
|
+
family: change
|
|
762
|
+
description: Read one continuous change unit from authoritative state.
|
|
763
|
+
phase: '9'
|
|
764
|
+
readonly: true
|
|
765
|
+
input_schema:
|
|
766
|
+
type: object
|
|
767
|
+
required: [id]
|
|
768
|
+
additionalProperties: false
|
|
769
|
+
properties:
|
|
770
|
+
id: { type: string }
|
|
771
|
+
output_schema:
|
|
772
|
+
type: object
|
|
773
|
+
required: [change]
|
|
774
|
+
additionalProperties: false
|
|
775
|
+
properties:
|
|
776
|
+
change: { type: object }
|
|
777
|
+
errors:
|
|
778
|
+
- { code: CHANGE_NOT_FOUND, description: 'change id does not exist' }
|
|
779
|
+
cli_subcommand: change get
|
|
780
|
+
emits_events: []
|
|
781
|
+
|
|
782
|
+
- name: change.list
|
|
783
|
+
family: change
|
|
784
|
+
description: List continuous changes by lifecycle status or change kind.
|
|
785
|
+
phase: '9'
|
|
786
|
+
readonly: true
|
|
787
|
+
input_schema:
|
|
788
|
+
type: object
|
|
789
|
+
additionalProperties: false
|
|
790
|
+
properties:
|
|
791
|
+
status: { type: string, enum: [active, blocked, ready, archived, cancelled] }
|
|
792
|
+
kind: { type: string, enum: [quick, standard, major, incident] }
|
|
793
|
+
limit: { type: integer, minimum: 1, maximum: 500 }
|
|
794
|
+
output_schema:
|
|
795
|
+
type: object
|
|
796
|
+
required: [changes, count]
|
|
797
|
+
additionalProperties: false
|
|
798
|
+
properties:
|
|
799
|
+
changes: { type: array, items: { type: object } }
|
|
800
|
+
count: { type: integer, minimum: 0 }
|
|
801
|
+
errors:
|
|
802
|
+
- { code: VALIDATION_ERROR, description: 'change filter failed validation' }
|
|
803
|
+
cli_subcommand: change list
|
|
804
|
+
emits_events: []
|
|
805
|
+
|
|
806
|
+
- name: change.context
|
|
807
|
+
family: change
|
|
808
|
+
description: Compile an inspectable task context manifest from Ultra state and external provider metadata references.
|
|
809
|
+
phase: '9'
|
|
810
|
+
writer_role: mcp
|
|
811
|
+
input_schema:
|
|
812
|
+
type: object
|
|
813
|
+
required: [id]
|
|
814
|
+
additionalProperties: false
|
|
815
|
+
properties:
|
|
816
|
+
id: { type: string }
|
|
817
|
+
task_id: { type: string }
|
|
818
|
+
spec_refs: { type: array, items: { type: object } }
|
|
819
|
+
allowed_paths: { type: array, items: { type: string } }
|
|
820
|
+
provider_refs:
|
|
821
|
+
type: object
|
|
822
|
+
additionalProperties: false
|
|
823
|
+
properties:
|
|
824
|
+
memory:
|
|
825
|
+
type: object
|
|
826
|
+
required: [provider]
|
|
827
|
+
additionalProperties: false
|
|
828
|
+
properties:
|
|
829
|
+
provider: { type: string }
|
|
830
|
+
project: { type: string }
|
|
831
|
+
reference: { type: string }
|
|
832
|
+
revision: { type: string }
|
|
833
|
+
indexed_head: { type: string }
|
|
834
|
+
status: { type: string, enum: [available, unavailable, fresh, stale, unknown] }
|
|
835
|
+
code_graph:
|
|
836
|
+
type: object
|
|
837
|
+
required: [provider]
|
|
838
|
+
additionalProperties: false
|
|
839
|
+
properties:
|
|
840
|
+
provider: { type: string }
|
|
841
|
+
project: { type: string }
|
|
842
|
+
reference: { type: string }
|
|
843
|
+
revision: { type: string }
|
|
844
|
+
indexed_head: { type: string }
|
|
845
|
+
status: { type: string, enum: [available, unavailable, fresh, stale, unknown] }
|
|
846
|
+
output_schema:
|
|
847
|
+
type: object
|
|
848
|
+
required: [manifest_path, manifest_hash, manifest]
|
|
849
|
+
additionalProperties: false
|
|
850
|
+
properties:
|
|
851
|
+
manifest_path: { type: string }
|
|
852
|
+
manifest_hash: { type: string, pattern: '^[0-9a-f]{64}$' }
|
|
853
|
+
manifest: { type: object }
|
|
854
|
+
errors:
|
|
855
|
+
- { code: CHANGE_NOT_FOUND, description: 'change id does not exist' }
|
|
856
|
+
- { code: CHANGE_TERMINAL, description: 'terminal change cannot compile a new context' }
|
|
857
|
+
- { code: PROVIDER_CONTENT_FORBIDDEN, description: 'provider payload content must remain external' }
|
|
858
|
+
cli_subcommand: change context
|
|
859
|
+
emits_events: [context_updated]
|
|
860
|
+
|
|
861
|
+
- name: change.converge
|
|
862
|
+
family: change
|
|
863
|
+
description: Deterministically verify task, spec, test, documentation, review, and context evidence before closure.
|
|
864
|
+
phase: '9'
|
|
865
|
+
writer_role: mcp
|
|
866
|
+
input_schema:
|
|
867
|
+
type: object
|
|
868
|
+
required: [id, evidence]
|
|
869
|
+
additionalProperties: false
|
|
870
|
+
properties:
|
|
871
|
+
id: { type: string }
|
|
872
|
+
evidence:
|
|
873
|
+
type: array
|
|
874
|
+
minItems: 1
|
|
875
|
+
items:
|
|
876
|
+
type: object
|
|
877
|
+
required: [category, status, evidence]
|
|
878
|
+
additionalProperties: false
|
|
879
|
+
properties:
|
|
880
|
+
category: { type: string, enum: [diagnosis, diff, tests, spec, docs, review] }
|
|
881
|
+
status: { type: string, enum: [pass, fail, not_applicable] }
|
|
882
|
+
evidence: { type: string, minLength: 3 }
|
|
883
|
+
output_schema:
|
|
884
|
+
type: object
|
|
885
|
+
required: [ready, status, blockers]
|
|
886
|
+
additionalProperties: false
|
|
887
|
+
properties:
|
|
888
|
+
ready: { type: boolean }
|
|
889
|
+
status: { type: string, enum: [blocked, ready] }
|
|
890
|
+
blockers: { type: array, items: { type: string } }
|
|
891
|
+
verification_path: { type: string }
|
|
892
|
+
errors:
|
|
893
|
+
- { code: CHANGE_NOT_FOUND, description: 'change id does not exist' }
|
|
894
|
+
- { code: CHANGE_NOT_CONVERGEABLE, description: 'change lifecycle does not allow convergence' }
|
|
895
|
+
cli_subcommand: change converge
|
|
896
|
+
emits_events: [change_blocked, change_converged]
|
|
897
|
+
|
|
898
|
+
- name: change.archive
|
|
899
|
+
family: change
|
|
900
|
+
description: Archive a converged change after explicit baseline-spec reconciliation.
|
|
901
|
+
phase: '9'
|
|
902
|
+
writer_role: mcp
|
|
903
|
+
input_schema:
|
|
904
|
+
type: object
|
|
905
|
+
required: [id, summary]
|
|
906
|
+
additionalProperties: false
|
|
907
|
+
properties:
|
|
908
|
+
id: { type: string }
|
|
909
|
+
summary: { type: string, minLength: 3 }
|
|
910
|
+
baseline_updates: { type: array, items: { type: string } }
|
|
911
|
+
no_baseline_change_reason: { type: string, minLength: 3 }
|
|
912
|
+
output_schema:
|
|
913
|
+
type: object
|
|
914
|
+
required: [change, archive_path]
|
|
915
|
+
additionalProperties: false
|
|
916
|
+
properties:
|
|
917
|
+
change: { type: object }
|
|
918
|
+
archive_path: { type: string }
|
|
919
|
+
errors:
|
|
920
|
+
- { code: CHANGE_NOT_READY, description: 'change has not passed convergence' }
|
|
921
|
+
- { code: BASELINE_RECONCILIATION_REQUIRED, description: 'baseline update or explicit no-change reason required' }
|
|
922
|
+
- { code: ARCHIVE_EXISTS, description: 'archive target already exists' }
|
|
923
|
+
cli_subcommand: change archive
|
|
924
|
+
emits_events: [change_archived]
|
|
925
|
+
|
|
926
|
+
# ────────────────────────── system family ──────────────────────────────
|
|
927
|
+
- name: system.doctor
|
|
928
|
+
family: system
|
|
929
|
+
description: Inspect Ultra runtime health and optionally perform backup-first recovery and projection repair.
|
|
930
|
+
phase: '9'
|
|
931
|
+
writer_role: mcp
|
|
932
|
+
input_schema:
|
|
933
|
+
type: object
|
|
934
|
+
additionalProperties: false
|
|
935
|
+
properties:
|
|
936
|
+
repair: { type: boolean, default: false }
|
|
937
|
+
output_schema:
|
|
938
|
+
type: object
|
|
939
|
+
required: [status, checks, repair_performed]
|
|
940
|
+
additionalProperties: true
|
|
941
|
+
properties:
|
|
942
|
+
status: { type: string, enum: [healthy, degraded] }
|
|
943
|
+
checks: { type: object }
|
|
944
|
+
repair_performed: { type: boolean }
|
|
945
|
+
backup_path: { type: string }
|
|
946
|
+
repair: { type: object }
|
|
947
|
+
errors:
|
|
948
|
+
- { code: BACKUP_FAILED, description: 'state database backup failed before repair' }
|
|
949
|
+
- { code: STATE_DB_ERROR, description: 'runtime state could not be inspected' }
|
|
950
|
+
cli_subcommand: system doctor
|
|
951
|
+
emits_events: []
|
|
952
|
+
|
|
617
953
|
# ────────────────────────── plan family (D34) ───────────────────────────
|
|
618
954
|
- name: plan.export
|
|
619
955
|
family: plan
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
},
|
|
61
61
|
"family": {
|
|
62
62
|
"type": "string",
|
|
63
|
-
"enum": ["task", "memory", "review", "impact", "skill", "session", "ask", "plan"]
|
|
63
|
+
"enum": ["task", "memory", "review", "impact", "skill", "session", "ask", "plan", "change", "system"]
|
|
64
64
|
},
|
|
65
65
|
"description": { "type": "string", "minLength": 10 },
|
|
66
66
|
"phase": {
|
|
@@ -12,6 +12,28 @@
|
|
|
12
12
|
-- PRAGMA busy_timeout=5000; -- block up to 5s on lock (R25)
|
|
13
13
|
-- PRAGMA foreign_keys=ON; -- enforce FK constraints
|
|
14
14
|
|
|
15
|
+
-- ──────────────────────────── changes ─────────────────────────────────────
|
|
16
|
+
-- A project baseline is continuous; each feature, fix, incident, or redesign
|
|
17
|
+
-- is an independently converged change unit. Only changes become terminal.
|
|
18
|
+
CREATE TABLE IF NOT EXISTS changes (
|
|
19
|
+
id TEXT PRIMARY KEY,
|
|
20
|
+
title TEXT NOT NULL,
|
|
21
|
+
kind TEXT NOT NULL CHECK (kind IN ('quick', 'standard', 'major', 'incident')),
|
|
22
|
+
status TEXT NOT NULL DEFAULT 'active'
|
|
23
|
+
CHECK (status IN ('active', 'blocked', 'ready', 'archived', 'cancelled')),
|
|
24
|
+
intent TEXT NOT NULL,
|
|
25
|
+
docs_impact_json TEXT NOT NULL DEFAULT '{"status":"unknown","files":[],"rationale":null}',
|
|
26
|
+
provider_refs_json TEXT NOT NULL DEFAULT '{}',
|
|
27
|
+
base_commit TEXT,
|
|
28
|
+
artifact_root TEXT NOT NULL,
|
|
29
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
30
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
31
|
+
closed_at TEXT
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
CREATE INDEX IF NOT EXISTS changes_status ON changes(status, created_at);
|
|
35
|
+
CREATE INDEX IF NOT EXISTS changes_kind ON changes(kind, created_at);
|
|
36
|
+
|
|
15
37
|
-- ──────────────────────────── tasks ───────────────────────────────────────
|
|
16
38
|
-- Authoritative task row. tasks.json is generated from this table by the
|
|
17
39
|
-- projector (Phase 2.6). Manual edits to tasks.json are overwritten.
|
|
@@ -33,6 +55,7 @@ CREATE TABLE IF NOT EXISTS tasks (
|
|
|
33
55
|
trace_to TEXT, -- spec anchor reference
|
|
34
56
|
context_file TEXT, -- projection target path
|
|
35
57
|
completion_commit TEXT, -- backfilled hash (Phase 2.8)
|
|
58
|
+
change_id TEXT REFERENCES changes(id) ON DELETE SET NULL,
|
|
36
59
|
parent_id TEXT REFERENCES tasks(id) ON DELETE SET NULL, -- Phase 8A.1: task.expand subtask→parent
|
|
37
60
|
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
38
61
|
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
|
@@ -43,6 +66,7 @@ CREATE INDEX IF NOT EXISTS tasks_tag ON tasks(tag);
|
|
|
43
66
|
CREATE INDEX IF NOT EXISTS tasks_session ON tasks(session_id) WHERE session_id IS NOT NULL;
|
|
44
67
|
CREATE INDEX IF NOT EXISTS tasks_stale ON tasks(stale) WHERE stale = 1;
|
|
45
68
|
CREATE INDEX IF NOT EXISTS tasks_parent ON tasks(parent_id) WHERE parent_id IS NOT NULL;
|
|
69
|
+
CREATE INDEX IF NOT EXISTS tasks_change ON tasks(change_id) WHERE change_id IS NOT NULL;
|
|
46
70
|
|
|
47
71
|
-- ──────────────────────────── events ──────────────────────────────────────
|
|
48
72
|
-- Append-only event stream. id is the subscription cursor (D31, R26):
|
|
@@ -53,6 +77,7 @@ CREATE TABLE IF NOT EXISTS events (
|
|
|
53
77
|
ts TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
54
78
|
type TEXT NOT NULL,
|
|
55
79
|
task_id TEXT,
|
|
80
|
+
change_id TEXT,
|
|
56
81
|
session_id TEXT,
|
|
57
82
|
runtime TEXT CHECK (runtime IS NULL OR runtime IN ('claude', 'opencode', 'codex')),
|
|
58
83
|
payload_json TEXT
|
|
@@ -61,6 +86,7 @@ CREATE TABLE IF NOT EXISTS events (
|
|
|
61
86
|
CREATE INDEX IF NOT EXISTS events_ts_type ON events(ts, type);
|
|
62
87
|
CREATE INDEX IF NOT EXISTS events_task ON events(task_id, id);
|
|
63
88
|
CREATE INDEX IF NOT EXISTS events_session ON events(session_id, id);
|
|
89
|
+
CREATE INDEX IF NOT EXISTS events_change ON events(change_id, id);
|
|
64
90
|
|
|
65
91
|
-- ──────────────────────────── sessions ────────────────────────────────────
|
|
66
92
|
-- Authoritative session row. lease_expires_at + heartbeat_at live ONLY here
|
|
@@ -146,6 +172,95 @@ CREATE TABLE IF NOT EXISTS circuit_breaker (
|
|
|
146
172
|
CREATE INDEX IF NOT EXISTS circuit_breaker_tripped ON circuit_breaker(tripped_at)
|
|
147
173
|
WHERE tripped_at IS NOT NULL;
|
|
148
174
|
|
|
175
|
+
-- ──────────────────────────── change artifacts ────────────────────────────
|
|
176
|
+
CREATE TABLE IF NOT EXISTS artifacts (
|
|
177
|
+
id TEXT PRIMARY KEY,
|
|
178
|
+
change_id TEXT NOT NULL REFERENCES changes(id) ON DELETE CASCADE,
|
|
179
|
+
task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
|
|
180
|
+
kind TEXT NOT NULL,
|
|
181
|
+
path TEXT NOT NULL,
|
|
182
|
+
content_hash TEXT,
|
|
183
|
+
metadata_json TEXT,
|
|
184
|
+
status TEXT NOT NULL DEFAULT 'current' CHECK (status IN ('current', 'archived')),
|
|
185
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
186
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
187
|
+
UNIQUE(change_id, kind, path)
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
CREATE INDEX IF NOT EXISTS artifacts_change ON artifacts(change_id, kind);
|
|
191
|
+
|
|
192
|
+
CREATE TABLE IF NOT EXISTS context_snapshots (
|
|
193
|
+
id TEXT PRIMARY KEY,
|
|
194
|
+
change_id TEXT NOT NULL REFERENCES changes(id) ON DELETE CASCADE,
|
|
195
|
+
task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
|
|
196
|
+
git_head TEXT,
|
|
197
|
+
provider_refs_json TEXT NOT NULL DEFAULT '{}',
|
|
198
|
+
manifest_path TEXT NOT NULL,
|
|
199
|
+
manifest_hash TEXT NOT NULL,
|
|
200
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
CREATE INDEX IF NOT EXISTS context_snapshots_change ON context_snapshots(change_id, created_at);
|
|
204
|
+
|
|
205
|
+
CREATE TABLE IF NOT EXISTS trace_links (
|
|
206
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
207
|
+
change_id TEXT NOT NULL REFERENCES changes(id) ON DELETE CASCADE,
|
|
208
|
+
task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
|
|
209
|
+
source_ref TEXT NOT NULL,
|
|
210
|
+
target_ref TEXT NOT NULL,
|
|
211
|
+
relation TEXT NOT NULL,
|
|
212
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
213
|
+
UNIQUE(change_id, source_ref, target_ref, relation)
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
CREATE INDEX IF NOT EXISTS trace_links_change ON trace_links(change_id, task_id);
|
|
217
|
+
|
|
218
|
+
-- ──────────────────────────── runtime reliability ─────────────────────────
|
|
219
|
+
CREATE TABLE IF NOT EXISTS incidents (
|
|
220
|
+
id TEXT PRIMARY KEY,
|
|
221
|
+
code TEXT NOT NULL,
|
|
222
|
+
severity TEXT NOT NULL CHECK (severity IN ('warning', 'error', 'critical')),
|
|
223
|
+
status TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('open', 'resolved')),
|
|
224
|
+
retryable INTEGER NOT NULL DEFAULT 0 CHECK (retryable IN (0, 1)),
|
|
225
|
+
message TEXT NOT NULL,
|
|
226
|
+
change_id TEXT REFERENCES changes(id) ON DELETE SET NULL,
|
|
227
|
+
task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
|
|
228
|
+
session_id TEXT REFERENCES sessions(sid) ON DELETE SET NULL,
|
|
229
|
+
source_kind TEXT,
|
|
230
|
+
source_id TEXT,
|
|
231
|
+
evidence_json TEXT,
|
|
232
|
+
occurrence_count INTEGER NOT NULL DEFAULT 1,
|
|
233
|
+
first_seen_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
234
|
+
last_seen_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
235
|
+
resolved_at TEXT,
|
|
236
|
+
resolution TEXT
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
CREATE INDEX IF NOT EXISTS incidents_open ON incidents(status, severity, last_seen_at);
|
|
240
|
+
CREATE INDEX IF NOT EXISTS incidents_change ON incidents(change_id, status);
|
|
241
|
+
|
|
242
|
+
CREATE TABLE IF NOT EXISTS projection_jobs (
|
|
243
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
244
|
+
tool_name TEXT NOT NULL,
|
|
245
|
+
event_cursor INTEGER NOT NULL DEFAULT 0,
|
|
246
|
+
status TEXT NOT NULL DEFAULT 'pending'
|
|
247
|
+
CHECK (status IN ('pending', 'running', 'completed', 'failed')),
|
|
248
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
249
|
+
max_attempts INTEGER NOT NULL DEFAULT 3 CHECK (max_attempts > 0),
|
|
250
|
+
last_error TEXT,
|
|
251
|
+
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
252
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
253
|
+
completed_at TEXT
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
CREATE INDEX IF NOT EXISTS projection_jobs_status ON projection_jobs(status, id);
|
|
257
|
+
|
|
258
|
+
CREATE TABLE IF NOT EXISTS event_consumers (
|
|
259
|
+
name TEXT PRIMARY KEY,
|
|
260
|
+
cursor INTEGER NOT NULL DEFAULT 0 CHECK (cursor >= 0),
|
|
261
|
+
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
|
|
262
|
+
);
|
|
263
|
+
|
|
149
264
|
-- ──────────────────────────── seed: schema_version ────────────────────────
|
|
150
265
|
INSERT OR IGNORE INTO schema_version (version, description)
|
|
151
266
|
VALUES ('4.5', 'Phase 2 initial — tasks/events/sessions/schema_version/migration_history/telemetry/specs_refs');
|
|
@@ -155,3 +270,5 @@ INSERT OR IGNORE INTO schema_version (version, description)
|
|
|
155
270
|
VALUES ('8A.1', 'Phase 8A.1 — tasks.parent_id for task.expand parent→children');
|
|
156
271
|
INSERT OR IGNORE INTO schema_version (version, description)
|
|
157
272
|
VALUES ('8A.2', 'Phase 8A.2 — tasks.estimated_days preserved across MCP and v4.4 migration');
|
|
273
|
+
INSERT OR IGNORE INTO schema_version (version, description)
|
|
274
|
+
VALUES ('9.0', 'Continuous change units, context snapshots, trace links, incidents, projection outbox, and durable consumers');
|
package/ultra-tools/cli.cjs
CHANGED
|
@@ -35,6 +35,7 @@ USAGE:
|
|
|
35
35
|
SUBCOMMANDS:
|
|
36
36
|
task init-project
|
|
37
37
|
session close | get | list | admission | heartbeat | subscribe | reap
|
|
38
|
+
system doctor [--repair]
|
|
38
39
|
status [--cost] [--since <duration>] [--json]
|
|
39
40
|
db init | checkpoint | vacuum | integrity | backup (Phase 2)
|
|
40
41
|
migrate --from=4.4 --to=4.5 [--dry|--rollback] (Phase 2)
|
|
@@ -65,6 +66,7 @@ const taskCommand = require('./commands/task.cjs');
|
|
|
65
66
|
const sessionCommand = require('./commands/session.cjs');
|
|
66
67
|
const statusCommand = require('./commands/status.cjs');
|
|
67
68
|
const legacyMemoryCommand = require('./commands/legacy-memory.cjs');
|
|
69
|
+
const systemCommand = require('./commands/system.cjs');
|
|
68
70
|
|
|
69
71
|
// Phase 6.2 — CLI telemetry: best-effort, never blocks the subcommand.
|
|
70
72
|
function emitCliTelemetry(sub, rest) {
|
|
@@ -93,6 +95,7 @@ const SUBCOMMANDS = {
|
|
|
93
95
|
db: (args) => process.exit(dbCommand.dispatch(args)),
|
|
94
96
|
migrate: (args) => process.exit(migrateCommand.dispatch(args)),
|
|
95
97
|
'legacy-memory': (args) => process.exit(legacyMemoryCommand.dispatch(args)),
|
|
98
|
+
system: (args) => systemCommand.dispatch(args).then((code) => process.exit(code)),
|
|
96
99
|
};
|
|
97
100
|
|
|
98
101
|
function main(argv) {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const { openStateDb, closeStateDb } = require('../../mcp-server/lib/state-db.cjs');
|
|
7
|
+
const doctor = require('../../mcp-server/lib/doctor.cjs');
|
|
8
|
+
|
|
9
|
+
const USAGE = `ultra-tools system doctor [--repair]\n`;
|
|
10
|
+
|
|
11
|
+
function emit(envelope) {
|
|
12
|
+
process.stdout.write(`${JSON.stringify(envelope)}\n`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function dispatchDoctor(args) {
|
|
16
|
+
const unknown = args.filter((arg) => !['--repair', '-h', '--help'].includes(arg));
|
|
17
|
+
if (args.includes('-h') || args.includes('--help')) {
|
|
18
|
+
process.stdout.write(USAGE);
|
|
19
|
+
return 0;
|
|
20
|
+
}
|
|
21
|
+
if (unknown.length > 0) {
|
|
22
|
+
emit({ ok: false, error: { code: 'VALIDATION_ERROR', message: `unknown system doctor flag: ${unknown[0]}` } });
|
|
23
|
+
return 1;
|
|
24
|
+
}
|
|
25
|
+
const rootDir = process.cwd();
|
|
26
|
+
const dbPath = path.join(rootDir, '.ultra', 'state.db');
|
|
27
|
+
if (!fs.existsSync(dbPath)) {
|
|
28
|
+
emit({ ok: false, error: { code: 'STATE_DB_MISSING', message: `.ultra/state.db not found under ${rootDir}` } });
|
|
29
|
+
return 2;
|
|
30
|
+
}
|
|
31
|
+
const db = openStateDb(dbPath);
|
|
32
|
+
try {
|
|
33
|
+
const data = await doctor.runDoctor(db, { rootDir, repair: args.includes('--repair') });
|
|
34
|
+
emit({ ok: true, data });
|
|
35
|
+
return data.status === 'healthy' ? 0 : 2;
|
|
36
|
+
} catch (error) {
|
|
37
|
+
emit({ ok: false, error: { code: error.code || 'STATE_DB_ERROR', message: error.message } });
|
|
38
|
+
return 2;
|
|
39
|
+
} finally {
|
|
40
|
+
closeStateDb(db);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function dispatch(args) {
|
|
45
|
+
const [verb, ...rest] = args;
|
|
46
|
+
if (!verb || verb === '-h' || verb === '--help') {
|
|
47
|
+
process.stdout.write(USAGE);
|
|
48
|
+
return 0;
|
|
49
|
+
}
|
|
50
|
+
if (verb !== 'doctor') {
|
|
51
|
+
emit({ ok: false, error: { code: 'UNKNOWN_VERB', message: `unknown system verb: ${verb}` } });
|
|
52
|
+
return 1;
|
|
53
|
+
}
|
|
54
|
+
return dispatchDoctor(rest);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = { USAGE, dispatch, dispatchDoctor };
|