ultra-builder-pro-cli 0.20.0 → 0.22.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/docs/research/README.md +26 -5
- package/.ultra-template/specs/discovery.md +1 -1
- package/.ultra-template/tasks/tasks.json +10 -3
- package/.ultra-template/templates/task-context.md +19 -0
- package/CHANGELOG.md +106 -0
- package/README.md +215 -41
- package/adapters/_shared/codex-assets.cjs +69 -11
- package/adapters/_shared/interaction-contract.cjs +36 -3
- package/adapters/_shared/runtime-assets.cjs +41 -1
- package/adapters/claude.js +7 -5
- package/adapters/codex.js +1 -1
- package/adapters/kimi.js +2 -8
- package/adapters/opencode.js +43 -30
- package/bin/orchestrator.js +64 -24
- package/docs/AGENT-CONTEXT.md +89 -49
- package/docs/ARCHITECTURE.md +112 -45
- package/docs/ARTIFACT-AUTHORITY.md +274 -0
- package/docs/COMMIT-HASH-BACKFILL.md +33 -90
- package/docs/DECISIONS.md +14 -6
- package/docs/LEGACY-CLI-CROSSWALK.md +16 -6
- package/docs/PLUGIN-ISOLATION-CONTRACT.md +6 -4
- package/docs/ROADMAP.md +20 -6
- package/docs/RUNTIME-COMPAT-MATRIX.md +29 -16
- package/docs/STATE-DB-ACCESS-POLICY.md +43 -13
- package/docs/WORKFLOW-LIFECYCLE.md +105 -50
- package/hooks/README.md +10 -8
- package/hooks/active_task_context.py +105 -18
- package/hooks/context_spine.py +16 -4
- package/hooks/health_check.py +5 -4
- package/hooks/pre_stop_check.py +5 -2
- package/hooks/runtime_paths.py +396 -0
- package/hooks/subagent_tracker.py +5 -2
- package/hooks/workflow_checkpoint.py +16 -3
- package/hooks/workflow_context.py +11 -3
- package/hooks/workflow_resume.py +7 -4
- package/mcp-server/breadcrumb.cjs +20 -2
- package/mcp-server/lib/archive-journal.cjs +1026 -111
- package/mcp-server/lib/archive-mutation-worker.py +365 -0
- package/mcp-server/lib/artifact-registry.cjs +1127 -0
- package/mcp-server/lib/baseline-workflow.cjs +69 -28
- package/mcp-server/lib/change-packet.cjs +818 -0
- package/mcp-server/lib/change-workflow.cjs +932 -176
- package/mcp-server/lib/context-paths.cjs +142 -0
- package/mcp-server/lib/context-spine.cjs +511 -45
- package/mcp-server/lib/decision-dialogue.cjs +345 -3
- package/mcp-server/lib/delivery-transaction.cjs +471 -0
- package/mcp-server/lib/doctor-backup-worker.cjs +328 -0
- package/mcp-server/lib/doctor.cjs +401 -11
- package/mcp-server/lib/git-bootstrap.cjs +458 -47
- package/mcp-server/lib/init-project.cjs +240 -95
- package/mcp-server/lib/plan-publication-worker.cjs +830 -0
- package/mcp-server/lib/plan-store.cjs +700 -5
- package/mcp-server/lib/project-breadcrumb.cjs +23 -4
- package/mcp-server/lib/projector.cjs +211 -42
- package/mcp-server/lib/runtime-paths.cjs +1687 -0
- package/mcp-server/lib/safe-project-file.cjs +385 -0
- package/mcp-server/lib/spec-learning.cjs +28 -12
- package/mcp-server/lib/state-authority.cjs +19 -3
- package/mcp-server/lib/state-db.cjs +263 -9
- package/mcp-server/lib/state-ops.cjs +254 -50
- package/mcp-server/lib/task-ledger.cjs +1342 -0
- package/mcp-server/lib/telemetry.cjs +12 -3
- package/mcp-server/lib/workflow-state.cjs +610 -58
- package/mcp-server/server.cjs +423 -42
- package/orchestrator/daemon.cjs +2 -10
- package/orchestrator/parallel-orchestrator.cjs +314 -50
- package/orchestrator/planner/plan-builder.cjs +1 -1
- package/orchestrator/recovery.cjs +125 -7
- package/orchestrator/session-close-journal-worker.cjs +5 -0
- package/orchestrator/session-close-journal.cjs +1014 -0
- package/orchestrator/session-runner.cjs +679 -69
- package/orchestrator/worktree-manager.cjs +186 -35
- package/package.json +2 -3
- package/skills/cc-collab/SKILL.md +4 -1
- package/skills/codex-collab/SKILL.md +5 -2
- package/skills/ultra-change/SKILL.md +34 -8
- package/skills/ultra-deliver/SKILL.md +26 -11
- package/skills/ultra-dev/SKILL.md +52 -10
- package/skills/ultra-doctor/SKILL.md +16 -3
- package/skills/ultra-init/SKILL.md +22 -11
- package/skills/ultra-plan/SKILL.md +58 -18
- package/skills/ultra-research/SKILL.md +37 -35
- package/skills/ultra-review/SKILL.md +14 -8
- package/skills/ultra-status/SKILL.md +18 -9
- package/skills/ultra-test/SKILL.md +11 -5
- package/skills/ultra-think/SKILL.md +6 -2
- package/skills/ultra-think/references/decision-dialogue.md +17 -6
- package/skills/ultra-verify/SKILL.md +4 -1
- package/spec/cli-protocol.md +21 -7
- package/spec/mcp-tools.yaml +629 -44
- package/spec/schemas/change-delta.v1.schema.json +153 -0
- package/spec/schemas/context-file.v4.5.schema.json +1 -1
- package/spec/schemas/documentation-reconciliation.v1.schema.json +106 -0
- package/spec/schemas/mcp-tools.schema.json +1 -1
- package/spec/schemas/state-db.sql +51 -14
- package/spec/schemas/task-ledger.v1.schema.json +217 -0
- package/spec/schemas/tasks.v4.5.schema.json +1 -1
- package/ultra-tools/cli.cjs +3 -2
- package/ultra-tools/commands/db.cjs +76 -25
- package/ultra-tools/commands/migrate.cjs +1000 -82
- package/ultra-tools/commands/session.cjs +34 -8
- package/ultra-tools/commands/status.cjs +32 -6
- package/ultra-tools/commands/system.cjs +129 -22
- package/.ultra-template/tasks/contexts/TEMPLATE.md +0 -19
- package/commands/learn.md +0 -10
- package/orchestrator/skill-miner.cjs +0 -133
- package/output-styles/coding-vibes.md +0 -67
- package/output-styles/structural-thinking.md +0 -61
- package/skills/learn/SKILL.md +0 -58
|
@@ -59,6 +59,49 @@ function gitBranch(rootDir) {
|
|
|
59
59
|
return result.status === 0 ? (result.stdout.trim() || null) : null;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
function gitCommitIsAncestor(rootDir, ancestor, descendant) {
|
|
63
|
+
if (!ancestor || !descendant) return false;
|
|
64
|
+
const result = spawnSync(
|
|
65
|
+
'git', ['merge-base', '--is-ancestor', ancestor, descendant],
|
|
66
|
+
{ cwd: rootDir, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] },
|
|
67
|
+
);
|
|
68
|
+
return result.status === 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function hashScopedWorkingTree(rootDir, relativePaths) {
|
|
72
|
+
const root = path.resolve(rootDir);
|
|
73
|
+
const digest = crypto.createHash('sha256');
|
|
74
|
+
for (const relative of [...new Set(relativePaths)].sort()) {
|
|
75
|
+
const file = path.resolve(root, relative);
|
|
76
|
+
if (file !== root && !file.startsWith(`${root}${path.sep}`)) {
|
|
77
|
+
throw new BaselineWorkflowError(
|
|
78
|
+
'BASELINE_SCOPE_INVALID',
|
|
79
|
+
`Git returned a path outside the repository: ${relative}`,
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
digest.update(relative).update('\0');
|
|
83
|
+
const stat = fs.lstatSync(file, { throwIfNoEntry: false });
|
|
84
|
+
if (!stat) {
|
|
85
|
+
digest.update('missing\0');
|
|
86
|
+
} else if (stat.isSymbolicLink()) {
|
|
87
|
+
digest.update('symlink\0').update(fs.readlinkSync(file)).update('\0');
|
|
88
|
+
} else if (stat.isFile()) {
|
|
89
|
+
digest.update(stat.mode & 0o111 ? 'file+x\0' : 'file\0');
|
|
90
|
+
digest.update(fs.readFileSync(file)).update('\0');
|
|
91
|
+
} else if (stat.isDirectory()) {
|
|
92
|
+
const submoduleHead = spawnSync('git', ['-C', file, 'rev-parse', 'HEAD'], {
|
|
93
|
+
cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'],
|
|
94
|
+
});
|
|
95
|
+
digest.update('directory\0');
|
|
96
|
+
if (submoduleHead.status === 0) digest.update(submoduleHead.stdout.trim());
|
|
97
|
+
digest.update('\0');
|
|
98
|
+
} else {
|
|
99
|
+
digest.update(`special:${stat.mode}\0`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return digest.digest('hex');
|
|
103
|
+
}
|
|
104
|
+
|
|
62
105
|
function gitWorktreeSnapshot(rootDir, scope = ['.']) {
|
|
63
106
|
const repositoryRoot = gitRepositoryRoot(rootDir);
|
|
64
107
|
if (!repositoryRoot) {
|
|
@@ -76,40 +119,24 @@ function gitWorktreeSnapshot(rootDir, scope = ['.']) {
|
|
|
76
119
|
'git', ['status', '--porcelain=v1', '-z', '--untracked-files=all', ...args],
|
|
77
120
|
{ cwd: rootDir, encoding: 'buffer', stdio: ['ignore', 'pipe', 'pipe'], maxBuffer: 32 * 1024 * 1024 },
|
|
78
121
|
);
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
'
|
|
82
|
-
{ cwd: rootDir, encoding: 'buffer', stdio: ['ignore', 'pipe', 'pipe'], maxBuffer: 32 * 1024 * 1024 },
|
|
83
|
-
)
|
|
84
|
-
: { status: 0, stdout: Buffer.alloc(0) };
|
|
85
|
-
const untracked = spawnSync(
|
|
86
|
-
'git', ['ls-files', '--others', '--exclude-standard', '-z', ...args],
|
|
122
|
+
const inventory = spawnSync(
|
|
123
|
+
'git', ['ls-files', '--cached', '--others', '--exclude-standard', '-z',
|
|
124
|
+
'--', ...pathspecs, ':(exclude).ultra', ':(exclude).ultra/**'],
|
|
87
125
|
{ cwd: rootDir, encoding: 'buffer', stdio: ['ignore', 'pipe', 'pipe'], maxBuffer: 32 * 1024 * 1024 },
|
|
88
126
|
);
|
|
89
|
-
if (status.status !== 0 ||
|
|
127
|
+
if (status.status !== 0 || inventory.status !== 0) {
|
|
90
128
|
return {
|
|
91
129
|
head, branch: gitBranch(rootDir), state: 'unavailable', digest: null, files: [],
|
|
92
130
|
};
|
|
93
131
|
}
|
|
94
132
|
const statusText = status.stdout.toString('utf8');
|
|
95
133
|
const files = statusText.split('\0').filter(Boolean);
|
|
96
|
-
const
|
|
97
|
-
digest.update(head || 'unborn').update('\0').update(gitBranch(rootDir) || '').update('\0');
|
|
98
|
-
digest.update(status.stdout).update(diff.stdout);
|
|
99
|
-
for (const relative of untracked.stdout.toString('utf8').split('\0').filter(Boolean).sort()) {
|
|
100
|
-
digest.update(relative).update('\0');
|
|
101
|
-
const file = path.resolve(rootDir, relative);
|
|
102
|
-
if (file.startsWith(`${path.resolve(rootDir)}${path.sep}`)
|
|
103
|
-
&& fs.existsSync(file) && fs.statSync(file).isFile()) {
|
|
104
|
-
digest.update(fs.readFileSync(file));
|
|
105
|
-
}
|
|
106
|
-
digest.update('\0');
|
|
107
|
-
}
|
|
134
|
+
const scopedFiles = inventory.stdout.toString('utf8').split('\0').filter(Boolean);
|
|
108
135
|
return {
|
|
109
136
|
head,
|
|
110
137
|
branch: gitBranch(rootDir),
|
|
111
138
|
state: head ? (files.length > 0 ? 'dirty' : 'clean') : 'unborn',
|
|
112
|
-
digest:
|
|
139
|
+
digest: hashScopedWorkingTree(rootDir, scopedFiles),
|
|
113
140
|
files,
|
|
114
141
|
};
|
|
115
142
|
}
|
|
@@ -693,11 +720,14 @@ function storedReadyInvariantBlockers(baseline) {
|
|
|
693
720
|
|
|
694
721
|
function completedBaselineResearch(db, baseline, rootDir) {
|
|
695
722
|
const acceptedMode = baseline.mode === 'greenfield' ? 'full' : 'adoption';
|
|
696
|
-
const
|
|
723
|
+
const rows = db.prepare(
|
|
697
724
|
`SELECT id FROM workflow_runs
|
|
698
725
|
WHERE kind = 'research' AND baseline_id = ? AND mode = ? AND status = 'completed'
|
|
699
|
-
ORDER BY completed_at DESC, rowid DESC
|
|
700
|
-
).
|
|
726
|
+
ORDER BY completed_at DESC, rowid DESC`,
|
|
727
|
+
).all(baseline.id, acceptedMode);
|
|
728
|
+
const row = rows.find((candidate) => workflows.isConsumableWorkflowAuthority(
|
|
729
|
+
workflows.readWorkflow(db, candidate.id, { rootDir }),
|
|
730
|
+
));
|
|
701
731
|
return row ? workflows.readWorkflow(db, row.id, { rootDir }) : null;
|
|
702
732
|
}
|
|
703
733
|
|
|
@@ -738,7 +768,12 @@ function convergenceBlockers(db, baseline, input, rootDir) {
|
|
|
738
768
|
else if (baseline.repository_revision !== expected) blockers.add('BASELINE_REVISION_MISMATCH');
|
|
739
769
|
const snapshot = gitWorktreeSnapshot(rootDir, baseline.scope);
|
|
740
770
|
if (snapshot.state === 'unborn') blockers.add('BASELINE_GIT_HEAD_REQUIRED');
|
|
741
|
-
if (snapshot.head && expected && snapshot.head !== expected
|
|
771
|
+
if (snapshot.head && expected && snapshot.head !== expected
|
|
772
|
+
&& (!gitCommitIsAncestor(rootDir, expected, snapshot.head)
|
|
773
|
+
|| (baseline.worktree_digest && snapshot.digest
|
|
774
|
+
&& baseline.worktree_digest !== snapshot.digest))) {
|
|
775
|
+
blockers.add('BASELINE_HEAD_STALE');
|
|
776
|
+
}
|
|
742
777
|
if (baseline.repository_branch && snapshot.branch
|
|
743
778
|
&& baseline.repository_branch !== snapshot.branch) blockers.add('BASELINE_BRANCH_STALE');
|
|
744
779
|
if (!Array.isArray(baseline.scope) || baseline.scope.length === 0) blockers.add('BASELINE_SCOPE_MISSING');
|
|
@@ -854,7 +889,11 @@ function inspectBaseline(db, { rootDir = process.cwd(), id } = {}) {
|
|
|
854
889
|
blockers.push('BASELINE_GIT_HEAD_REQUIRED');
|
|
855
890
|
}
|
|
856
891
|
if (snapshot.head && baseline.repository_revision && snapshot.head !== baseline.repository_revision) {
|
|
857
|
-
|
|
892
|
+
if (!gitCommitIsAncestor(rootDir, baseline.repository_revision, snapshot.head)
|
|
893
|
+
|| (baseline.worktree_digest && snapshot.digest
|
|
894
|
+
&& baseline.worktree_digest !== snapshot.digest)) {
|
|
895
|
+
blockers.push('BASELINE_HEAD_STALE');
|
|
896
|
+
}
|
|
858
897
|
} else if (!snapshot.head && baseline.repository_revision?.startsWith('workspace:')) {
|
|
859
898
|
const revision = workspaceRevision({
|
|
860
899
|
scope: baseline.scope, specs: baseline.spec_refs, evidence: baseline.evidence,
|
|
@@ -883,6 +922,7 @@ function inferSpecKind(file) {
|
|
|
883
922
|
|
|
884
923
|
function reconcileBaseline(db, {
|
|
885
924
|
baseline_updates = [], change_id = null, reconciliation = null,
|
|
925
|
+
accept_delivery_worktree = false,
|
|
886
926
|
} = {}, {
|
|
887
927
|
rootDir = process.cwd(), emitEvent = true,
|
|
888
928
|
} = {}) {
|
|
@@ -926,11 +966,12 @@ function reconcileBaseline(db, {
|
|
|
926
966
|
ops.tx(db, () => {
|
|
927
967
|
db.prepare(
|
|
928
968
|
`UPDATE baselines SET repository_revision = ?, repository_branch = ?, worktree_state = ?,
|
|
929
|
-
worktree_digest = ?, worktree_files_json = ?, worktree_accepted =
|
|
969
|
+
worktree_digest = ?, worktree_files_json = ?, worktree_accepted = ?,
|
|
930
970
|
spec_refs_json = ?, evidence_json = ?, gaps_json = ?, unknowns_json = ?,
|
|
931
971
|
updated_at = ? WHERE id = ?`,
|
|
932
972
|
).run(
|
|
933
973
|
revision, snapshot.branch, snapshot.state, snapshot.digest, JSON.stringify(snapshot.files),
|
|
974
|
+
accept_delivery_worktree && snapshot.state === 'dirty' ? 1 : 0,
|
|
934
975
|
JSON.stringify(specs), JSON.stringify(evidence), JSON.stringify(gaps),
|
|
935
976
|
JSON.stringify(unknowns), ts, current.id,
|
|
936
977
|
);
|