dsh-tower 2.5.0 → 2.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/CHANGELOG.md +114 -0
- package/COMPATIBILITY.md +2 -0
- package/CONTRIBUTING.md +1 -1
- package/GLOSSARY.md +6 -0
- package/PRINCIPLES.md +15 -7
- package/README.en.md +18 -16
- package/README.md +18 -11
- package/adapters/leases.js +20 -6
- package/adapters/legacy.js +149 -37
- package/adapters/stateFile.js +11 -1
- package/app/archive.js +30 -5
- package/app/briefing.js +95 -12
- package/app/checkCompletion.js +107 -18
- package/app/enrichedMissions.js +1 -1
- package/app/freshness.js +77 -0
- package/app/gate.js +26 -12
- package/app/merge.js +7 -4
- package/app/mission.js +24 -1
- package/app/missionContext.js +2 -1
- package/app/plan.js +64 -1
- package/app/provision.js +133 -22
- package/app/read.js +38 -5
- package/app/rebase.js +10 -8
- package/app/review.js +104 -61
- package/app/reviewCheckout.js +133 -0
- package/app/sandbox.js +121 -0
- package/app/sharedCheckout.js +90 -0
- package/app/spawn-prechecks.js +31 -20
- package/app/spawn-support.js +59 -8
- package/app/spawn.js +3 -0
- package/app/teardown.js +119 -2
- package/app/tower.js +34 -6
- package/client.js +321 -0
- package/cordis.patch.yml +55 -3
- package/docs/adr/0005-component-surface.md +58 -13
- package/docs/adr/0006-mission-shapes.md +306 -0
- package/docs/architecture/overview.md +18 -10
- package/docs/examples/first-mission.md +3 -2
- package/docs/faq.md +9 -7
- package/docs/guide/install-upgrade-migration.md +58 -7
- package/docs/guide/quickstart.md +20 -10
- package/docs/guide/scenarios-and-philosophy.md +35 -28
- package/docs/operations/runbook.md +1 -1
- package/docs/operations/security.md +11 -10
- package/docs/reference/blockers.md +20 -11
- package/docs/reference/component-surface.md +20 -8
- package/docs/reference/config.generated.md +8 -5
- package/docs/reference/config.md +30 -5
- package/docs/reference/doctor.md +7 -7
- package/docs/reference/errors.generated.md +1 -0
- package/docs/reference/errors.md +2 -1
- package/docs/reference/operations.md +1 -1
- package/docs/reference/shapes.md +123 -0
- package/docs/reference/surface-budget.md +53 -4
- package/docs/reference/testing.md +5 -3
- package/docs/reference/tools.generated.md +7 -7
- package/docs/reference/tools.md +5 -5
- package/docs/zh/faq.md +7 -5
- package/docs/zh/guide/install-upgrade-migration.md +25 -6
- package/docs/zh/guide/quickstart.md +17 -10
- package/docs/zh/guide/scenarios-and-philosophy.md +23 -15
- package/docs/zh/operations/security.md +9 -8
- package/docs/zh/reference/blockers.md +13 -9
- package/docs/zh/reference/config.md +22 -5
- package/docs/zh/reference/errors.md +2 -1
- package/docs/zh/reference/testing.md +5 -4
- package/docs/zh/reference/tools.md +3 -3
- package/domain/batch.js +75 -105
- package/domain/collab/batch.js +30 -10
- package/domain/collab/spawn.js +53 -16
- package/domain/components.js +12 -4
- package/domain/config/schema.js +87 -22
- package/domain/contracts/outputs.js +2 -1
- package/domain/contracts/tool-inputs/missions.js +10 -2
- package/domain/errors.js +6 -0
- package/domain/evidence.js +283 -0
- package/domain/gate.js +17 -34
- package/domain/integration/gate.js +17 -43
- package/domain/integration/init.js +3 -2
- package/domain/integration/provision.js +51 -0
- package/domain/integration/rebase.js +16 -1
- package/domain/integration/review.js +203 -9
- package/domain/mission.js +16 -33
- package/domain/missions/completion.js +46 -8
- package/domain/missions/findings.js +2 -1
- package/domain/missions/lifecycle.js +131 -35
- package/domain/missions/plan.js +30 -6
- package/domain/missions/review.js +2 -18
- package/domain/planning.js +5 -5
- package/domain/read/missionContext.js +17 -3
- package/domain/read/views.js +5 -1
- package/domain/roles.js +222 -0
- package/domain/roster.js +9 -3
- package/domain/scope.js +51 -0
- package/domain/shapes/build.js +254 -0
- package/domain/shapes/index.js +171 -0
- package/domain/shapes/survey.js +445 -0
- package/domain/state/archive.js +28 -4
- package/domain/state/migrations.js +111 -4
- package/domain/state/schema.js +69 -7
- package/domain/state/semantics.js +93 -13
- package/domain/state.js +5 -1
- package/domain/summary.js +5 -11
- package/domain/vocabulary.js +15 -33
- package/host/capability-registry.js +25 -9
- package/host/config.js +40 -3
- package/host/doctor.js +20 -8
- package/host/preset-survey-sandbox.js +19 -0
- package/host/preset-survey.js +18 -0
- package/host/presets.js +17 -11
- package/host/tool-registry.js +23 -13
- package/locale/en.json +16 -1
- package/locale/preset-survey/en.json +6 -0
- package/locale/preset-survey/zh.json +6 -0
- package/locale/preset-survey-sandbox/en.json +6 -0
- package/locale/preset-survey-sandbox/zh.json +6 -0
- package/locale/zh.json +16 -1
- package/package.json +19 -2
- package/ports/state.js +8 -1
- package/scripts/architecture-waivers.json +3 -3
- package/scripts/check-components.mjs +120 -6
- package/scripts/check-docs.mjs +1 -1
- package/scripts/check-pack.mjs +5 -0
- package/scripts/component-surface.json +83 -29
- package/scripts/generate-capability-locale.mjs +5 -0
- package/scripts/generate-shapes-reference.mjs +171 -0
- package/scripts/mount-smoke/surface.mjs +3 -0
- package/scripts/pack-manifest.txt +22 -0
- package/scripts/shape-audit.mjs +361 -0
- package/scripts/surface-baseline.json +75 -4
- package/scripts/types-coverage.mjs +7 -0
- package/survey-sandbox.js +48 -0
- package/survey.js +40 -0
- package/tools/complete.js +5 -5
- package/tools/diff.js +5 -3
- package/tools/plan.js +1 -1
- package/tools/preflight.js +3 -2
- package/tools/review.js +1 -1
- package/tools/spawn.js +48 -50
- package/tools/status.js +8 -1
- package/tools/test.js +2 -1
- package/types/index.d.ts +76 -5
package/tools/preflight.js
CHANGED
|
@@ -12,7 +12,7 @@ export function registerPreflightTool(ctx, deps) {
|
|
|
12
12
|
define(ctx, {
|
|
13
13
|
name: 'tower_preflight',
|
|
14
14
|
description:
|
|
15
|
-
'Read-only merge gate for one mission: returns ok plus stable blocker codes (branch-missing, deps-unmerged,
|
|
15
|
+
'Read-only merge gate for one mission: returns ok plus stable blocker codes (branch-missing, deps-unmerged, stale-base, out-of-scope, base-dirty, branch-owned-by-closed-missions, and mission/worker guards) and warnings (survey-stale). Review blockers (no-review, review-hold, not-clean, review-not-approved, tip-moved, review-mission-mismatch) apply only when review.mode is required; review.mode off and optional skip them. survey-stale reports a build dependency on a survey whose readCommit is not an ancestor-or-equal of the build base; it is a warning by default and a blocker only when gates.requireFreshSurvey is true. A survey has no merge gate: it is refused with invalid-transition (tower_complete checks HEAD/readCommit and zero commits). Never changes anything.',
|
|
16
16
|
parameters: PreflightInput,
|
|
17
17
|
aliases: {"missionId":["id"]},
|
|
18
18
|
requiresMissionId: true,
|
|
@@ -20,10 +20,11 @@ export function registerPreflightTool(ctx, deps) {
|
|
|
20
20
|
async execute(args) {
|
|
21
21
|
const tower = await deps.openTower(args.repoRoot)
|
|
22
22
|
const { activity, parentStatus } = await observeActivity(ctx, tower)
|
|
23
|
-
const gate = await tower.evaluateGate({ missionId: args.missionId, activity, parentStatus })
|
|
23
|
+
const gate = await tower.evaluateGate({ missionId: args.missionId, activity, parentStatus, action: 'preflight' })
|
|
24
24
|
return {
|
|
25
25
|
ok: gate.ok,
|
|
26
26
|
blockers: gate.blockers,
|
|
27
|
+
warnings: gate.warnings ?? [],
|
|
27
28
|
missionId: args.missionId,
|
|
28
29
|
branchTip: gate.branchTip ?? null,
|
|
29
30
|
baseTip: gate.baseTip ?? null,
|
package/tools/review.js
CHANGED
|
@@ -13,7 +13,7 @@ export function registerReviewTool(ctx, deps) {
|
|
|
13
13
|
define(ctx, {
|
|
14
14
|
name: 'tower_review',
|
|
15
15
|
description:
|
|
16
|
-
'Submit an independent review verdict bound to the current branch tip: a completed mission starts a review round, and while the mission is active a later route may still record a verdict for that same reviewed tip, appending to the same round without changing the status (a moved tip is rejected with an explanation). status is "clean", "p1-Nitems", or "p2-Nitems"; merge is "merge", "fix-then-merge", or "hold" (only clean + merge is later approval). Non-clean returns the mission to active. Reviewers are capped at 5 rounds per branch; a tower-submitted review is break-glass and requires breakGlassReason.',
|
|
16
|
+
'Submit an independent review verdict bound to the current branch tip: a completed mission starts a review round, and while the mission is active a later route may still record a verdict for that same reviewed tip, appending to the same round without changing the status (a moved tip is rejected with an explanation). status is "clean", "p1-Nitems", or "p2-Nitems"; merge is "merge", "fix-then-merge", or "hold" (only clean + merge is later approval). Non-clean returns the mission to active. Reviewers are capped at 5 rounds per branch; a tower-submitted review is break-glass and requires breakGlassReason. The tool is not registered when review.mode is off.',
|
|
17
17
|
parameters: ReviewInput,
|
|
18
18
|
aliases: {"missionId":["id"]},
|
|
19
19
|
requiresMissionId: true,
|
package/tools/spawn.js
CHANGED
|
@@ -2,47 +2,27 @@
|
|
|
2
2
|
import { SCHEMAS, output } from '../domain/contracts/outputs.js'
|
|
3
3
|
import { TowerToolError, classifyError } from '../domain/errors.js'
|
|
4
4
|
import { SpawnInput } from '../domain/contracts/tool-inputs/index.js'
|
|
5
|
-
import {
|
|
5
|
+
import { defaultPresetFor, shapeFor } from '../domain/shapes/index.js'
|
|
6
|
+
import { DEFAULT_ROLE, ROLES as ROLE_ENUM, SPAWN_OUTCOME, isCompletedStopReason, isSpawnAdmission } from '../domain/vocabulary.js'
|
|
7
|
+
import { resolveRoleSpawn, roleSpawnDiscovery } from '../domain/roles.js'
|
|
6
8
|
import {
|
|
7
9
|
assertSpawnableMission,
|
|
8
10
|
precheckSpawnServices,
|
|
9
11
|
precheckWorkerOwnership,
|
|
10
12
|
resolveRosterName,
|
|
11
13
|
} from '../app/spawn-prechecks.js'
|
|
12
|
-
import { buildAgentOptions,
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* @param {any}
|
|
16
|
-
* @param {{
|
|
17
|
-
* define: import('../types/index.js').DefineTool,
|
|
18
|
-
* openTower: any,
|
|
19
|
-
* actorFor: any,
|
|
20
|
-
* requireTowerActor: any,
|
|
21
|
-
* enqueueSpawn: any,
|
|
22
|
-
* observeActivity: any,
|
|
23
|
-
* spawnBackoffState: any,
|
|
24
|
-
* recordSpawnThrottle: any,
|
|
25
|
-
* recordSpawnSuccess: any,
|
|
26
|
-
* runMissionDiscovery: any,
|
|
27
|
-
* textOf: any, inspectLeases: any,
|
|
28
|
-
* leaseComponent?: () => { enabled: boolean, reason: string },
|
|
29
|
-
* }} deps
|
|
30
|
-
*/
|
|
14
|
+
import { buildAgentOptions, provisionSpawnPlacement, waitForSpawnResult } from '../app/spawn-support.js'
|
|
15
|
+
import { cleanupReviewCheckout } from '../app/reviewCheckout.js'
|
|
16
|
+
/** @param {any} ctx
|
|
17
|
+
* @param {{ define: import('../types/index.js').DefineTool, [key: string]: any }} deps */
|
|
31
18
|
export function registerSpawnTool(ctx, deps) {
|
|
32
|
-
const {
|
|
33
|
-
define, actorFor, requireTowerActor, enqueueSpawn, observeActivity,
|
|
34
|
-
spawnBackoffState, recordSpawnThrottle, recordSpawnSuccess,
|
|
35
|
-
runMissionDiscovery, textOf,
|
|
36
|
-
} = deps
|
|
19
|
+
const { define, actorFor, requireTowerActor, enqueueSpawn, observeActivity, spawnBackoffState, recordSpawnThrottle, recordSpawnSuccess, runMissionDiscovery, textOf } = deps
|
|
37
20
|
define(ctx, {
|
|
38
21
|
name: 'tower_spawn',
|
|
39
22
|
description:
|
|
40
|
-
'Spawn a worker or reviewer for a mission. Workers use the tower-worker
|
|
41
|
-
parameters: SpawnInput,
|
|
23
|
+
'Spawn a worker or reviewer for a mission. Workers use the mission shape\'s preset (tower-worker for a build, tower-survey for a read-only shared survey, tower-survey-sandbox for a detached survey with survey.sandbox=detached); reviewers require a completed build mission and use the read-only tower-reviewer preset, and the reviewer role is refused when review.mode is off. A build provisions (or reuses) the mission git worktree and branch; a shared survey runs in the shared checkout because it reads one commit; a detached survey provisions (or reuses) a disposable detached worktree at its readCommit with no branch. Generates a self-contained briefing (including dependency survey result evidence) and starts a background subagent. The child final report is delivered back to this session; tower_roster shows authoritative activity. Records the agent in the roster; reviewer spawns get a disposable detached checkout at the recorded branch tip, and the verdict is stamped with that commit (never a live worktree HEAD). Returns immediately unless wait=true.', parameters: SpawnInput,
|
|
42
24
|
aliases: {"missionId":["id"],"waitMs":["wait_ms"]},
|
|
43
|
-
requiresMissionId: true,
|
|
44
|
-
board: true,
|
|
45
|
-
output: output(SCHEMAS.spawn),
|
|
25
|
+
requiresMissionId: true, board: true, output: output(SCHEMAS.spawn),
|
|
46
26
|
async execute(args, exec) {
|
|
47
27
|
const tower = await deps.openTower(args.repoRoot)
|
|
48
28
|
const actor = await actorFor(tower, exec)
|
|
@@ -51,10 +31,18 @@ export function registerSpawnTool(ctx, deps) {
|
|
|
51
31
|
if (!ROLE_ENUM.includes(role)) throw new TowerToolError('invalid-argument', `role must be one of ${ROLE_ENUM.join('|')}`)
|
|
52
32
|
// Repository config (if any) already overrides the profile config here.
|
|
53
33
|
const config = tower.config
|
|
54
|
-
const preset = isReviewerRole(role) ? config.spawn.reviewerPreset : config.spawn.workerPreset
|
|
55
|
-
const model = isReviewerRole(role) ? config.spawn.reviewerModel : config.spawn.workerModel
|
|
56
34
|
const context = await tower.missionContext(args.missionId)
|
|
57
|
-
assertSpawnableMission({ role, context })
|
|
35
|
+
assertSpawnableMission({ role, context, config })
|
|
36
|
+
const shape = shapeFor(context.mission.kind)
|
|
37
|
+
// #148 W2: the shape resolves its preset from tuning config, so a detached
|
|
38
|
+
// survey gets the sandbox preset and a shared survey stays read-only.
|
|
39
|
+
// #149 W4: role resolution (preset row, model route, placement, briefing)
|
|
40
|
+
// is one contract, orthogonal to the shape that supplies the facts.
|
|
41
|
+
const shapePreset = defaultPresetFor(context.mission.kind, config, context.mission)
|
|
42
|
+
const roleSpawn = resolveRoleSpawn(role, { config, shape, shapePreset })
|
|
43
|
+
if (!roleSpawn) throw new TowerToolError('invalid-argument', `role must be one of ${ROLE_ENUM.join('|')}`)
|
|
44
|
+
const preset = roleSpawn.preset
|
|
45
|
+
const model = roleSpawn.model
|
|
58
46
|
const spawned = await enqueueSpawn(tower.root, async () => {
|
|
59
47
|
const observed = await observeActivity(ctx, tower)
|
|
60
48
|
await tower.reconcileObservedInactive(observed.activity)
|
|
@@ -65,26 +53,25 @@ export function registerSpawnTool(ctx, deps) {
|
|
|
65
53
|
}
|
|
66
54
|
const view = await tower.rosterView()
|
|
67
55
|
const name = resolveRosterName({ role, missionId: args.missionId, requested: args.name, roster: view.roster })
|
|
68
|
-
const takeover = await precheckWorkerOwnership({
|
|
69
|
-
role, tower, ctx, view, missionId: args.missionId, takeover: args.takeover,
|
|
70
|
-
maxLiveWorkers: config.spawn.maxLiveWorkers, budget: backoff.budget, observeActivity,
|
|
71
|
-
})
|
|
56
|
+
const takeover = await precheckWorkerOwnership({ role, tower, ctx, view, missionId: args.missionId, takeover: args.takeover, maxLiveWorkers: config.spawn.maxLiveWorkers, budget: backoff.budget, observeActivity })
|
|
72
57
|
const { subagents, provider } = await precheckSpawnServices({
|
|
73
58
|
tower, ctx, config, preset, actor, exec, inspectLeases: deps.inspectLeases,
|
|
74
59
|
// #131: the app layer only needs the row fact; the root owns the registry.
|
|
75
60
|
leaseComponent: deps.leaseComponent,
|
|
76
61
|
})
|
|
77
62
|
|
|
78
|
-
const
|
|
79
|
-
|
|
63
|
+
const { mission, worktree, cwd, branch, review, briefing, provisioning, created, sandbox } = await provisionSpawnPlacement(tower, {
|
|
64
|
+
role,
|
|
65
|
+
context,
|
|
66
|
+
missionId: /** @type {string} */ (args.missionId),
|
|
67
|
+
name,
|
|
80
68
|
worktreeRoot: args.worktreeRoot,
|
|
81
|
-
includeDirtyBase: args.includeDirtyBase
|
|
69
|
+
includeDirtyBase: args.includeDirtyBase,
|
|
70
|
+
instructions: args.instructions,
|
|
82
71
|
})
|
|
83
|
-
const { mission, worktree } = provisioned
|
|
84
|
-
const briefing = buildSpawnBriefing({ role, towerRoot: tower.root, context, worktree, instructions: args.instructions })
|
|
85
72
|
const agentOptions = buildAgentOptions(model)
|
|
86
73
|
|
|
87
|
-
if (
|
|
74
|
+
if (roleSpawnDiscovery(role, shape) && isSpawnAdmission(tower.config.tests?.admission)) {
|
|
88
75
|
const discovery = await runMissionDiscovery(tower, mission)
|
|
89
76
|
if (discovery.configured && discovery.receipt.ok !== true) {
|
|
90
77
|
throw new TowerToolError(
|
|
@@ -102,15 +89,17 @@ export function registerSpawnTool(ctx, deps) {
|
|
|
102
89
|
name,
|
|
103
90
|
role,
|
|
104
91
|
parentSessionId: String(exec.agent.id),
|
|
105
|
-
cwd
|
|
106
|
-
branch
|
|
92
|
+
cwd,
|
|
93
|
+
branch,
|
|
94
|
+
reviewCommit: review?.commit,
|
|
95
|
+
reviewCheckout: review?.path,
|
|
107
96
|
})
|
|
108
97
|
try {
|
|
109
98
|
activation = await subagents.startActivation({
|
|
110
99
|
provider,
|
|
111
100
|
label: name,
|
|
112
101
|
request: {
|
|
113
|
-
cwd
|
|
102
|
+
cwd,
|
|
114
103
|
agentPreset: preset,
|
|
115
104
|
prompt: [{ type: 'text', text: briefing }],
|
|
116
105
|
parent: exec.agent,
|
|
@@ -127,8 +116,10 @@ export function registerSpawnTool(ctx, deps) {
|
|
|
127
116
|
role,
|
|
128
117
|
agentId,
|
|
129
118
|
parentSessionId: String(exec.agent.id),
|
|
130
|
-
cwd
|
|
131
|
-
branch
|
|
119
|
+
cwd,
|
|
120
|
+
branch,
|
|
121
|
+
reviewCommit: review?.commit,
|
|
122
|
+
reviewCheckout: review?.path,
|
|
132
123
|
takeover,
|
|
133
124
|
})
|
|
134
125
|
recordSpawnSuccess(tower.root, config.spawn.maxLiveWorkers)
|
|
@@ -152,6 +143,10 @@ export function registerSpawnTool(ctx, deps) {
|
|
|
152
143
|
detail: `spawn failed${agentId ? ` after activation ${agentId}` : ' before activation'}: ${String(error?.message ?? error).slice(0, 300)}`,
|
|
153
144
|
})
|
|
154
145
|
.catch(() => {})
|
|
146
|
+
// A failed review spawn must not leak its detached evidence checkout.
|
|
147
|
+
if (review) {
|
|
148
|
+
await cleanupReviewCheckout(tower, { name, missionId: mission.id }).catch(() => {})
|
|
149
|
+
}
|
|
155
150
|
throw error
|
|
156
151
|
}
|
|
157
152
|
|
|
@@ -167,8 +162,11 @@ export function registerSpawnTool(ctx, deps) {
|
|
|
167
162
|
provider,
|
|
168
163
|
maxDepth: config.spawn.maxDepth ?? 1,
|
|
169
164
|
...(Object.keys(agentOptions).length > 0 ? { agentOptions } : {}),
|
|
170
|
-
worktree: { path: worktree.path, branch: worktree.branch, baseCommit: worktree.baseCommit },
|
|
171
|
-
|
|
165
|
+
worktree: worktree ? { path: worktree.path, branch: worktree.branch, baseCommit: worktree.baseCommit } : null,
|
|
166
|
+
sandbox,
|
|
167
|
+
review: review ? { commit: review.commit, checkout: review.path } : null,
|
|
168
|
+
provisioning,
|
|
169
|
+
created,
|
|
172
170
|
briefingChars: briefing.length,
|
|
173
171
|
takeover,
|
|
174
172
|
rosterEntry: entry,
|
package/tools/status.js
CHANGED
|
@@ -4,6 +4,7 @@ import { classifyError } from '../domain/errors.js'
|
|
|
4
4
|
import { StatusInput } from '../domain/contracts/tool-inputs/index.js'
|
|
5
5
|
import { ROSTER_INACTIVE_STATES, isMarkdownFormat, isReservedState, isWorkerRole } from '../domain/vocabulary.js'
|
|
6
6
|
import { archiveView } from '../app/archive.js'
|
|
7
|
+
import { reviewPolicyView } from '../domain/integration/review.js'
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* @param {any} ctx
|
|
@@ -21,7 +22,7 @@ export function registerStatusTool(ctx, deps) {
|
|
|
21
22
|
define(ctx, {
|
|
22
23
|
name: 'tower_status',
|
|
23
24
|
description:
|
|
24
|
-
'Read the Tower dashboard for a repository: state path, base branch, revision, mission status counts, paged mission summaries (scope/deps/tasks/worktree/owner), roster size, findings, archive count, config path and the activity tail. Also reports the repo-scoped git lock holder (`gitLock: {held,pid,phase,heldMs}`) so a merge holding the mutex is visible. Pass missionId for one full mission (tasks, notes, reviews, findings, completion report; archived missions are loaded from their year file); offset/limit page the mission list (default 20, max 100); includeGate adds preflight gate digests for the page; includeArchived appends compact archived summaries after the active page; format:"markdown" returns the human-readable mission board (active missions plus an archived count). Read-only.',
|
|
25
|
+
'Read the Tower dashboard for a repository: state path, base branch, revision, mission status counts, paged mission summaries (scope/deps/tasks/worktree/owner), roster size, findings, archive count, config path and the activity tail. Also reports the repo-scoped git lock holder (`gitLock: {held,pid,phase,heldMs}`) so a merge holding the mutex is visible, and the resolved review policy (`review: {mode, available, required, detail}`; mode off means review is intentionally unavailable). Pass missionId for one full mission (tasks, notes, reviews, findings, completion report; archived missions are loaded from their year file); offset/limit page the mission list (default 20, max 100); includeGate adds preflight gate digests for the page; includeArchived appends compact archived summaries after the active page; format:"markdown" returns the human-readable mission board (active missions plus an archived count). Read-only.',
|
|
25
26
|
parameters: StatusInput,
|
|
26
27
|
aliases: { mission_id: ['missionId'], include_gate: ['includeGate'], include_archived: ['includeArchived'] },
|
|
27
28
|
output: output(SCHEMAS.status),
|
|
@@ -38,6 +39,10 @@ export function registerStatusTool(ctx, deps) {
|
|
|
38
39
|
(entry) => isWorkerRole(entry.role) && !ROSTER_INACTIVE_STATES.includes(entry.state) && !isReservedState(entry.state),
|
|
39
40
|
).length
|
|
40
41
|
const spawn = spawnBackoffView(tower.root, tower.config.spawn?.maxLiveWorkers, inflight)
|
|
42
|
+
// #M39: the resolved review policy (`{mode, available, required, detail}`)
|
|
43
|
+
// is a first-class dashboard fact. In `off` the detail says review is
|
|
44
|
+
// intentionally unavailable rather than leaving it as a silent absence.
|
|
45
|
+
const review = reviewPolicyView(tower.config)
|
|
41
46
|
if (args.missionId) {
|
|
42
47
|
const [status, detail] = await Promise.all([
|
|
43
48
|
tower.status({ missionIds: [args.missionId] }),
|
|
@@ -51,6 +56,7 @@ export function registerStatusTool(ctx, deps) {
|
|
|
51
56
|
boardPath,
|
|
52
57
|
mode,
|
|
53
58
|
spawn,
|
|
59
|
+
review,
|
|
54
60
|
components,
|
|
55
61
|
}
|
|
56
62
|
}
|
|
@@ -96,6 +102,7 @@ export function registerStatusTool(ctx, deps) {
|
|
|
96
102
|
boardPath,
|
|
97
103
|
mode,
|
|
98
104
|
spawn,
|
|
105
|
+
review,
|
|
99
106
|
components,
|
|
100
107
|
}
|
|
101
108
|
if (isMarkdownFormat(args.format)) {
|
package/tools/test.js
CHANGED
|
@@ -46,6 +46,8 @@ export function registerTestTool(ctx, deps) {
|
|
|
46
46
|
return { ok: false, configured: false, error, tests: [], commands: [] }
|
|
47
47
|
}
|
|
48
48
|
const receipt = discovery.receipt
|
|
49
|
+
// #149 W4: the receipt is returned as recorded; there is no derived
|
|
50
|
+
// `checks` copy. Completion/batch read it through domain/evidence.js.
|
|
49
51
|
return {
|
|
50
52
|
ok: receipt.ok === true,
|
|
51
53
|
configured: true,
|
|
@@ -54,7 +56,6 @@ export function registerTestTool(ctx, deps) {
|
|
|
54
56
|
commands: receipt.commands ?? [],
|
|
55
57
|
error: receipt.error ?? undefined,
|
|
56
58
|
receipt,
|
|
57
|
-
checks: discovery.check ? [discovery.check] : [],
|
|
58
59
|
}
|
|
59
60
|
},
|
|
60
61
|
})
|
package/types/index.d.ts
CHANGED
|
@@ -57,6 +57,7 @@ export type ErrorCode =
|
|
|
57
57
|
| 'provider-cooldown'
|
|
58
58
|
| 'preset-missing'
|
|
59
59
|
| 'reviewer-prerequisite'
|
|
60
|
+
| 'review-disabled'
|
|
60
61
|
| 'not-authorized'
|
|
61
62
|
| 'workspace-busy'
|
|
62
63
|
| 'worker-limit'
|
|
@@ -73,6 +74,15 @@ export type ErrorCode =
|
|
|
73
74
|
| 'tool-internal'
|
|
74
75
|
// generated:vocabulary:end
|
|
75
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Review policy mode (#M39): `off` (review intentionally unavailable),
|
|
79
|
+
* `optional` (available, never required; default) or `required` (a clean review
|
|
80
|
+
* bound to the branch tip is a merge blocker). The deprecated
|
|
81
|
+
* `review.requireReview` boolean normalizes to this: true -> required,
|
|
82
|
+
* false -> optional.
|
|
83
|
+
*/
|
|
84
|
+
export type ReviewMode = 'off' | 'optional' | 'required'
|
|
85
|
+
|
|
76
86
|
/** One machine-readable gate/completion blocker: a stable code plus a detail. */
|
|
77
87
|
export interface Blocker {
|
|
78
88
|
code: string
|
|
@@ -139,11 +149,25 @@ export interface RebaseRecord {
|
|
|
139
149
|
baseTip?: string
|
|
140
150
|
}
|
|
141
151
|
|
|
142
|
-
|
|
152
|
+
/** Evidence a survey may record when it completes (optional). */
|
|
153
|
+
export interface SurveyResult {
|
|
154
|
+
paths?: string[]
|
|
155
|
+
symbols?: string[]
|
|
156
|
+
/** Ordered plan / next steps the read recommends (bounded array of strings). */
|
|
157
|
+
plan?: string[]
|
|
158
|
+
notes?: string
|
|
159
|
+
[key: string]: unknown
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Fields every mission carries regardless of shape. The per-shape arms below
|
|
164
|
+
* declare which document dimensions a kind owns; the state schema v4 union and
|
|
165
|
+
* `domain/state/semantics.js` enforce the same split at runtime.
|
|
166
|
+
*/
|
|
167
|
+
export interface MissionBase {
|
|
143
168
|
id: string
|
|
144
169
|
title: string
|
|
145
170
|
status: MissionStatus
|
|
146
|
-
kind: MissionKind
|
|
147
171
|
scope: string[]
|
|
148
172
|
deps: string[]
|
|
149
173
|
/**
|
|
@@ -155,22 +179,59 @@ export interface Mission {
|
|
|
155
179
|
blockers?: string[]
|
|
156
180
|
reviewPolicy?: ReviewPolicy
|
|
157
181
|
reviewExemptionReason?: string | null
|
|
158
|
-
worktree?: MissionWorktree | null
|
|
159
182
|
owner?: string | null
|
|
160
183
|
notes?: string[]
|
|
161
184
|
completionReport?: CompletionReport | null
|
|
162
185
|
testRuns?: TestRun[]
|
|
163
186
|
rebase?: RebaseRecord | null
|
|
164
|
-
mergedCommit?: string | null
|
|
165
|
-
mergedAt?: number | null
|
|
166
187
|
scopeGeneration?: number
|
|
167
188
|
ownerGeneration?: number
|
|
168
189
|
pauseCause?: string | null
|
|
169
190
|
updatedAt?: number
|
|
170
191
|
references?: string[]
|
|
171
192
|
authorizationRef?: string | null
|
|
193
|
+
/**
|
|
194
|
+
* Survey evidence; the completion transition refuses a result on a shape that
|
|
195
|
+
* does not accept one, so the type stays permissive for the shared draft.
|
|
196
|
+
*/
|
|
197
|
+
result?: SurveyResult | null
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** A build mission owns a private branch/worktree and lands by merging. */
|
|
201
|
+
export interface BuildMission extends MissionBase {
|
|
202
|
+
kind: 'build'
|
|
203
|
+
worktree?: MissionWorktree | null
|
|
204
|
+
mergedCommit?: string | null
|
|
205
|
+
mergedAt?: number | null
|
|
206
|
+
readCommit?: never
|
|
207
|
+
readDirty?: never
|
|
208
|
+
sandbox?: never
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** A survey reads one commit in the shared checkout; it never merges. */
|
|
212
|
+
export interface SurveyMission extends MissionBase {
|
|
213
|
+
kind: 'survey'
|
|
214
|
+
readCommit: string
|
|
215
|
+
readDirty?: string[]
|
|
216
|
+
result?: SurveyResult | null
|
|
217
|
+
/**
|
|
218
|
+
* #148 W2: the disposable detached sandbox path when `survey.sandbox` is
|
|
219
|
+
* `detached`. It has no branch and is removed on completion/abandon; a
|
|
220
|
+
* cleanup failure keeps the path plus `sandboxCleanupError` so the record and
|
|
221
|
+
* the filesystem do not silently disagree.
|
|
222
|
+
*/
|
|
223
|
+
sandbox?: string
|
|
224
|
+
sandboxCleanupError?: string
|
|
225
|
+
sandboxRemovedAt?: number
|
|
226
|
+
worktree?: never
|
|
227
|
+
branch?: never
|
|
228
|
+
baseCommit?: never
|
|
229
|
+
mergedCommit?: never
|
|
230
|
+
mergedAt?: never
|
|
172
231
|
}
|
|
173
232
|
|
|
233
|
+
export type Mission = BuildMission | SurveyMission
|
|
234
|
+
|
|
174
235
|
/**
|
|
175
236
|
* Compact archived-mission entry (`archive/index.json`). It keeps enough of the
|
|
176
237
|
* mission for ordering, dependency lookups and id allocation without loading the
|
|
@@ -245,6 +306,10 @@ export interface RosterEntry {
|
|
|
245
306
|
missionId?: string | null
|
|
246
307
|
parentSessionId?: string | null
|
|
247
308
|
spawnTip?: string | null
|
|
309
|
+
/** The commit a reviewer's verdict is stamped with (#149 W3). */
|
|
310
|
+
reviewCommit?: string | null
|
|
311
|
+
/** The disposable detached checkout the reviewer reads, when recorded. */
|
|
312
|
+
reviewCheckout?: string | null
|
|
248
313
|
stale?: boolean
|
|
249
314
|
[key: string]: unknown
|
|
250
315
|
}
|
|
@@ -316,6 +381,12 @@ export interface MissionSummary {
|
|
|
316
381
|
reviewPolicy: ReviewPolicy
|
|
317
382
|
mergedCommit: string | null
|
|
318
383
|
mergedAt: number | null
|
|
384
|
+
/** Survey projection: the commit the survey read (null for a build). */
|
|
385
|
+
readCommit?: string | null
|
|
386
|
+
/** Survey projection: the tracked-status fingerprint captured at plan time. */
|
|
387
|
+
readDirty?: string[]
|
|
388
|
+
/** Survey projection: the disposable detached sandbox path (null otherwise). */
|
|
389
|
+
sandbox?: string | null
|
|
319
390
|
/** Size of the completion report without loading its body; null when absent. */
|
|
320
391
|
reportBytes: number | null
|
|
321
392
|
updatedAt?: number
|