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
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Tower survey preset declaration (#148 D7): the read-only survey preset's own
|
|
2
|
+
// Plugins-page label. This is the survey twin of `host/preset-worker.js`: the row
|
|
3
|
+
// config stays in `cordis.patch.yml` and this module forwards it to the injected
|
|
4
|
+
// `agentPresets` service, which owns mount/unmount. A Tower-owned specifier gives
|
|
5
|
+
// the row a distinct locale (`dsh-tower/preset-survey/locale/<lang>.json`).
|
|
6
|
+
export const name = 'dsh-tower/preset-survey'
|
|
7
|
+
|
|
8
|
+
export const inject = ['agentPresets']
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Register the survey preset with the profile's preset registry.
|
|
12
|
+
* @param {{ agentPresets: { register: (config: any) => Promise<() => Promise<void> | void> } }} ctx
|
|
13
|
+
* @param {any} config the `cordis.patch.yml` row config (id/name/description/order/plugins)
|
|
14
|
+
* @returns {Promise<() => Promise<void> | void>} the registry's unregister disposer
|
|
15
|
+
*/
|
|
16
|
+
export async function apply(ctx, config) {
|
|
17
|
+
return ctx.agentPresets.register(config)
|
|
18
|
+
}
|
package/host/presets.js
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
// 1. `preset-tower-worker` is a mandatory dependency: planning, mode entry and
|
|
7
7
|
// spawn must refuse when the worker preset is absent.
|
|
8
8
|
// 2. `preset-tower-reviewer` is a *conditional* component. It may be off while
|
|
9
|
-
// `review.
|
|
10
|
-
//
|
|
11
|
-
// allowed.
|
|
9
|
+
// `review.mode` is `off` or `optional` (merge is unaffected; `off` does not
|
|
10
|
+
// use the preset at all); with `review.mode:required` it must be locked with
|
|
11
|
+
// an explanatory error, never silently allowed.
|
|
12
12
|
//
|
|
13
13
|
// `tower-tests` adds a third: with the component row off there is no
|
|
14
14
|
// `tower_test` tool and no receipt, so `gates.requireTests: true` is refused
|
|
@@ -19,13 +19,19 @@
|
|
|
19
19
|
// spawn would fail at `lease-busy` advising a tool that is not registered, so the
|
|
20
20
|
// contradiction is refused up front instead.
|
|
21
21
|
import { TowerToolError } from '../domain/errors.js'
|
|
22
|
+
import { reviewRequired } from '../domain/integration/review.js'
|
|
23
|
+
import { ROLES, rolePresetConfig } from '../domain/roles.js'
|
|
22
24
|
import { componentEnabled, disabledReason } from './capability-registry.js'
|
|
23
25
|
|
|
24
26
|
export function presetIds(config = {}) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
const ids = {}
|
|
28
|
+
const spawn = config.spawn ?? {}
|
|
29
|
+
for (const role of ROLES) {
|
|
30
|
+
const spec = rolePresetConfig(role)
|
|
31
|
+
if (!spec) continue
|
|
32
|
+
ids[role] = String(spawn[spec.slot] ?? spec.fallback)
|
|
28
33
|
}
|
|
34
|
+
return ids
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
function rowId(preset) {
|
|
@@ -38,22 +44,22 @@ function rowId(preset) {
|
|
|
38
44
|
* registered. If the service is unavailable, doctor reports that separately and
|
|
39
45
|
* this check cannot make a claim either way.
|
|
40
46
|
*
|
|
41
|
-
* The reviewer preset is required only when `review.
|
|
42
|
-
* off state is
|
|
47
|
+
* The reviewer preset is required only when `review.mode` is `required`; with
|
|
48
|
+
* `off`/`optional` the off state is supported by the component contract.
|
|
43
49
|
*/
|
|
44
50
|
export async function assertPresetsAvailable(ctx, config = {}) {
|
|
45
51
|
const service = ctx?.get?.('agentPresets')
|
|
46
52
|
if (!service || typeof service.list !== 'function') return { checked: false, missing: [] }
|
|
47
53
|
const presets = presetIds(config)
|
|
48
54
|
const required = [presets.worker]
|
|
49
|
-
if (config
|
|
55
|
+
if (reviewRequired(config)) required.push(presets.reviewer)
|
|
50
56
|
const available = new Set((await service.list()).map((entry) => String(entry.id)))
|
|
51
57
|
const missing = required.filter((id) => !available.has(id))
|
|
52
58
|
if (missing.length === 0) return { checked: true, missing: [] }
|
|
53
59
|
const rows = [...new Set(missing.map(rowId))]
|
|
54
60
|
const reviewLock =
|
|
55
|
-
config
|
|
56
|
-
? ` review.
|
|
61
|
+
reviewRequired(config) && missing.includes(presets.reviewer)
|
|
62
|
+
? ` review.mode is required, so the reviewer row may not be off; ${disabledReason(rowId(presets.reviewer))} is rejected`
|
|
57
63
|
: ''
|
|
58
64
|
throw new TowerToolError(
|
|
59
65
|
'preset-missing',
|
package/host/tool-registry.js
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
} from '../domain/vocabulary.js'
|
|
17
17
|
import { git, resolveRepo, hasAnyCommit } from '../adapters/git.js'
|
|
18
18
|
import { runtimeConfig } from './config.js'
|
|
19
|
+
import { reviewUnavailable } from '../domain/integration/review.js'
|
|
19
20
|
import { runDoctor } from './doctor.js'
|
|
20
21
|
import { createHmrBridge } from './hmr.js'
|
|
21
22
|
import { assertRuntimePreconditions } from './presets.js'
|
|
@@ -53,7 +54,7 @@ import { registerFindingsTool } from '../tools/findings.js'
|
|
|
53
54
|
import { readDiscoveryConfig, normalizeDiscovery, runDiscovery } from '../adapters/tests.js'
|
|
54
55
|
import { summarize as summarizeBatch } from '../domain/batch.js'
|
|
55
56
|
import { assertPorts } from '../ports/index.js'
|
|
56
|
-
import { COMPONENT_TOOLS, componentEnabled, componentReport, holdComponent, disabledReason } from './capability-registry.js'
|
|
57
|
+
import { COMPONENT_TOOLS, componentEnabled, componentReport, holdComponent, disabledReason, presetAvailable, PRESET_ROW_COMPONENTS } from './capability-registry.js'
|
|
57
58
|
import path from 'node:path'
|
|
58
59
|
import { fsp } from '../adapters/store-fs.js'
|
|
59
60
|
import { createDefaultDeps } from '../adapters/default.js'
|
|
@@ -510,16 +511,9 @@ async function runMissionDiscovery(tower, mission) {
|
|
|
510
511
|
diagnosticsDir: path.join(tower.paths.dir, 'test-runs', `${mission.id}-${Date.now()}`),
|
|
511
512
|
})
|
|
512
513
|
const stored = await tower.recordTestRun({ missionId: mission.id, receipt })
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
command: commands.map((command) => `${command.id}: ${command.command}`).join('; ') || 'tower.testDiscovery',
|
|
517
|
-
exitCode: commands.every((command) => command.exitCode === 0) ? 0 : (commands.find((command) => command.exitCode !== 0)?.exitCode ?? 1),
|
|
518
|
-
evidence: stored.ok
|
|
519
|
-
? `${stored.tests?.length ?? 0} test(s) passed at ${String(stored.treeTip ?? '').slice(0, 12)}; receipt ${stored.receiptPath ?? stored.diagnosticsPath}`
|
|
520
|
-
: `test discovery failed: ${String(stored.error ?? 'unknown').slice(0, 300)}`,
|
|
521
|
-
}
|
|
522
|
-
return { configured: true, receipt: stored, check }
|
|
514
|
+
// #149 W4: the recorded receipt is the only evidence shape. Consumers read it
|
|
515
|
+
// through domain/evidence.js; there is no separate `check` projection.
|
|
516
|
+
return { configured: true, receipt: stored }
|
|
523
517
|
}
|
|
524
518
|
|
|
525
519
|
async function observeActivity(ctx, tower) {
|
|
@@ -579,6 +573,15 @@ export function registerCoreTowerTools(ctx, options = {}) {
|
|
|
579
573
|
components: {
|
|
580
574
|
enabled: (id) => componentEnabled(id),
|
|
581
575
|
reason: (id) => disabledReason(id),
|
|
576
|
+
// Preset rows are declarative and invisible to the code registry, so the
|
|
577
|
+
// synchronous `enabled` probe cannot answer for them. Callers that gate a
|
|
578
|
+
// shape read this async probe, which returns null when `agentPresets` is
|
|
579
|
+
// absent (unknown, not disabled).
|
|
580
|
+
available: async (id) => {
|
|
581
|
+
const presetId = PRESET_ROW_COMPONENTS[id]
|
|
582
|
+
if (presetId) return presetAvailable(ctx, presetId)
|
|
583
|
+
return componentEnabled(id)
|
|
584
|
+
},
|
|
582
585
|
},
|
|
583
586
|
events: {
|
|
584
587
|
emit(name, payload) {
|
|
@@ -638,7 +641,7 @@ export function registerCoreTowerTools(ctx, options = {}) {
|
|
|
638
641
|
|
|
639
642
|
registerInterruptTool(ctx, { openTower, define, actorFor, requireTowerActor, interruptRosterAgent })
|
|
640
643
|
|
|
641
|
-
registerDiffTool(ctx, { openTower, define })
|
|
644
|
+
registerDiffTool(ctx, { openTower, define, actorFor })
|
|
642
645
|
|
|
643
646
|
registerAdoptTool(ctx, { openTower, define, actorFor, requireTowerActor })
|
|
644
647
|
|
|
@@ -652,7 +655,14 @@ export function registerCoreTowerTools(ctx, options = {}) {
|
|
|
652
655
|
|
|
653
656
|
registerFindingsTool(ctx, { openTower, define, actorFor, requireKnownActor })
|
|
654
657
|
|
|
655
|
-
|
|
658
|
+
// #M39: `review.mode:off` is the one configuration that removes tower_review
|
|
659
|
+
// from the mounted surface. The name is still recorded before the decision so
|
|
660
|
+
// the concept budget keeps measuring the build surface (the same rule the
|
|
661
|
+
// shipped-off component rows follow), and status/doctor report the mode as
|
|
662
|
+
// intentionally unavailable. A repository layer cannot re-enable the tool
|
|
663
|
+
// (host/config.js refuses that); the profile owns the mounted surface.
|
|
664
|
+
recordToolRegistration('tower_review')
|
|
665
|
+
if (!reviewUnavailable(runtimeConfig())) registerReviewTool(ctx, { openTower, define, actorFor })
|
|
656
666
|
|
|
657
667
|
registerPreflightTool(ctx, { openTower, define, observeActivity })
|
|
658
668
|
|
package/locale/en.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"meta": {
|
|
3
3
|
"title": "Tower",
|
|
4
|
-
"description": "Plan and track missions, isolate each one in its own worktree, hand the work to worker and reviewer agents, and merge after review when review before merging is required. Off equals uninstalling Tower: no tools, presets or skills remain."
|
|
4
|
+
"description": "Plan and track missions, isolate each one in its own worktree, hand the work to worker and reviewer agents, and merge after review when review before merging is required. Off equals uninstalling Tower: no tools, presets or skills remain. Turning off the survey capability removes both survey presets: surveys cannot be planned (planning one is refused with an actionable code) and no survey agent can be dispatched. Build missions and every other capability keep working."
|
|
5
5
|
},
|
|
6
6
|
"components": {
|
|
7
7
|
"tower": {
|
|
@@ -59,6 +59,21 @@
|
|
|
59
59
|
"summary": "The independent Tower Reviewer preset: a read-only reviewer agent that reads the mission diff, checks scope and acceptance, and returns a verdict. Usable while review before merging is optional; a precondition for merging once review before merging is required.",
|
|
60
60
|
"disables": "The reviewer cannot be started. That is supported while review before merging is optional; once review before merging is required, planning and entering Tower mode are refused with an explanation."
|
|
61
61
|
},
|
|
62
|
+
"tower-survey": {
|
|
63
|
+
"title": "Tower Survey",
|
|
64
|
+
"summary": "Inspect one recorded commit either read-only in the shared checkout or in a disposable detached sandbox that may write and run commands inside it. A survey has no branch, no commit of its own, and never merges; the sandbox is removed when the survey completes.",
|
|
65
|
+
"disables": "Turning off the survey capability removes both survey presets: surveys cannot be planned (planning one is refused with an actionable code) and no survey agent can be dispatched. Build missions and every other capability keep working."
|
|
66
|
+
},
|
|
67
|
+
"preset-tower-survey": {
|
|
68
|
+
"title": "Tower Survey preset",
|
|
69
|
+
"summary": "The shared-checkout survey dependency: read, glob, grep, skill, and the Tower mission/status/finding/inbox/complete tools, with no write, edit, or shell tool.",
|
|
70
|
+
"disables": "A shared survey cannot be dispatched; build missions and every other capability keep working."
|
|
71
|
+
},
|
|
72
|
+
"preset-tower-survey-sandbox": {
|
|
73
|
+
"title": "Tower Survey Sandbox preset",
|
|
74
|
+
"summary": "The detached-survey dependency: a disposable sandbox that may write and run commands inside its checkout while reading one recorded commit, with no branch and no commit of its own.",
|
|
75
|
+
"disables": "A detached survey cannot be dispatched; shared surveys and build missions keep working."
|
|
76
|
+
},
|
|
62
77
|
"tower-ui": {
|
|
63
78
|
"title": "Tower UI",
|
|
64
79
|
"summary": "Adds Tower cards for tool results, a compact status summary above the message box, and a short status strip, in the web client.",
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"meta": {
|
|
3
|
+
"title": "Tower Survey preset",
|
|
4
|
+
"description": "The shared-checkout survey dependency: read, glob, grep, skill, and the Tower mission/status/finding/inbox/complete tools, with no write, edit, or shell tool. Mandatory dependency, not a switch: A shared survey cannot be dispatched; build missions and every other capability keep working."
|
|
5
|
+
}
|
|
6
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{
|
|
2
|
+
"meta": {
|
|
3
|
+
"title": "Tower Survey Sandbox preset",
|
|
4
|
+
"description": "The detached-survey dependency: a disposable sandbox that may write and run commands inside its checkout while reading one recorded commit, with no branch and no commit of its own. Mandatory dependency, not a switch: A detached survey cannot be dispatched; shared surveys and build missions keep working."
|
|
5
|
+
}
|
|
6
|
+
}
|
package/locale/zh.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"meta": {
|
|
3
3
|
"title": "Tower",
|
|
4
|
-
"description": "规划并跟踪任务线,让每条任务线在独立工作树中完成,交给执行代理与审查代理,并且在把合并前审查设为必须时,审查通过后再合并。关闭这一项等同于卸载 Tower
|
|
4
|
+
"description": "规划并跟踪任务线,让每条任务线在独立工作树中完成,交给执行代理与审查代理,并且在把合并前审查设为必须时,审查通过后再合并。关闭这一项等同于卸载 Tower:不再保留任何工具、预设或技能。关闭勘察能力会移除两个勘察预设:无法规划勘察任务(规划勘察会被可操作的错误码拒绝),也无法派发勘察代理。普通构建任务线与其他能力继续工作。"
|
|
5
5
|
},
|
|
6
6
|
"components": {
|
|
7
7
|
"tower": {
|
|
@@ -59,6 +59,21 @@
|
|
|
59
59
|
"summary": "独立的 Tower Reviewer 审查代理预设:一个只读代理,阅读任务线差异、核对范围与验收,并给出结论。在合并前审查仍为可选时可以使用;一旦把合并前审查设为必须,它就是合并的前置条件。",
|
|
60
60
|
"disables": "无法启动审查代理。在合并前审查仍为可选时,允许关闭这一项;一旦要求合并前必须审查,规划或进入 Tower 模式会被拒绝并给出解释。"
|
|
61
61
|
},
|
|
62
|
+
"tower-survey": {
|
|
63
|
+
"title": "Tower 勘察",
|
|
64
|
+
"summary": "勘察一个已记录的提交:可在共享检出中只读查看,也可在一次性分离沙箱中写入并运行命令。勘察没有分支、没有自己的提交,也从不合并;勘察完成时沙箱会被移除。",
|
|
65
|
+
"disables": "关闭勘察能力会移除两个勘察预设:无法规划勘察任务(规划勘察会被可操作的错误码拒绝),也无法派发勘察代理。普通构建任务线与其他能力继续工作。"
|
|
66
|
+
},
|
|
67
|
+
"preset-tower-survey": {
|
|
68
|
+
"title": "Tower Survey 预设",
|
|
69
|
+
"summary": "共享检出勘察的依赖项:read、glob、grep、skill,以及 Tower 的任务线、状态、发现、收件箱与完成工具,不包含写入、编辑或命令行工具。",
|
|
70
|
+
"disables": "无法派发共享检出勘察;普通构建任务线与其他能力继续工作。"
|
|
71
|
+
},
|
|
72
|
+
"preset-tower-survey-sandbox": {
|
|
73
|
+
"title": "Tower Survey Sandbox 预设",
|
|
74
|
+
"summary": "分离勘察的依赖项:一个一次性沙箱,可在读取一个已记录提交时于检出中写入并运行命令,没有分支,也没有自己的提交。",
|
|
75
|
+
"disables": "无法派发分离勘察;共享检出勘察与普通构建任务线继续工作。"
|
|
76
|
+
},
|
|
62
77
|
"tower-ui": {
|
|
63
78
|
"title": "Tower 界面",
|
|
64
79
|
"summary": "在 Web 客户端中显示 Tower 工具结果卡片、输入框上方的简要状态摘要,以及简短的状态提示条。",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-tower",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "DSH-native Tower: mission planning, worktree isolation, review-gated merges, and a human-owned Tower mode.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,8 +28,11 @@
|
|
|
28
28
|
},
|
|
29
29
|
"exports": {
|
|
30
30
|
".": "./index.js",
|
|
31
|
+
"./client": "./client.js",
|
|
31
32
|
"./worker": "./worker.js",
|
|
32
33
|
"./reviewer": "./reviewer.js",
|
|
34
|
+
"./survey": "./survey.js",
|
|
35
|
+
"./survey-sandbox": "./survey-sandbox.js",
|
|
33
36
|
"./skills": "./skills.js",
|
|
34
37
|
"./batch": "./host/capability-batch.js",
|
|
35
38
|
"./import": "./host/capability-import.js",
|
|
@@ -40,6 +43,8 @@
|
|
|
40
43
|
"./hmr": "./host/capability-hmr.js",
|
|
41
44
|
"./preset-worker": "./host/preset-worker.js",
|
|
42
45
|
"./preset-reviewer": "./host/preset-reviewer.js",
|
|
46
|
+
"./preset-survey": "./host/preset-survey.js",
|
|
47
|
+
"./preset-survey-sandbox": "./host/preset-survey-sandbox.js",
|
|
43
48
|
"./skills/locale/*.json": "./locale/skills/*.json",
|
|
44
49
|
"./batch/locale/*.json": "./locale/batch/*.json",
|
|
45
50
|
"./import/locale/*.json": "./locale/import/*.json",
|
|
@@ -50,14 +55,19 @@
|
|
|
50
55
|
"./hmr/locale/*.json": "./locale/hmr/*.json",
|
|
51
56
|
"./preset-worker/locale/*.json": "./locale/preset-worker/*.json",
|
|
52
57
|
"./preset-reviewer/locale/*.json": "./locale/preset-reviewer/*.json",
|
|
58
|
+
"./preset-survey/locale/*.json": "./locale/preset-survey/*.json",
|
|
59
|
+
"./preset-survey-sandbox/locale/*.json": "./locale/preset-survey-sandbox/*.json",
|
|
53
60
|
"./locale/*.json": "./locale/*.json",
|
|
54
61
|
"./icon": "./icon.svg",
|
|
55
62
|
"./package.json": "./package.json"
|
|
56
63
|
},
|
|
57
64
|
"files": [
|
|
65
|
+
"client.js",
|
|
58
66
|
"index.js",
|
|
59
67
|
"worker.js",
|
|
60
68
|
"reviewer.js",
|
|
69
|
+
"survey.js",
|
|
70
|
+
"survey-sandbox.js",
|
|
61
71
|
"skills.js",
|
|
62
72
|
"skills/",
|
|
63
73
|
"host/*.js",
|
|
@@ -86,6 +96,10 @@
|
|
|
86
96
|
"dsh": {
|
|
87
97
|
"bundle": {
|
|
88
98
|
"patch": "./cordis.patch.yml"
|
|
99
|
+
},
|
|
100
|
+
"client": {
|
|
101
|
+
"platform": "web",
|
|
102
|
+
"immediately": true
|
|
89
103
|
}
|
|
90
104
|
},
|
|
91
105
|
"scripts": {
|
|
@@ -119,6 +133,9 @@
|
|
|
119
133
|
"docs:check:generated:components": "node scripts/generate-component-surface.mjs --check",
|
|
120
134
|
"docs:generate:locale": "node scripts/generate-capability-locale.mjs",
|
|
121
135
|
"docs:check:generated:locale": "node scripts/generate-capability-locale.mjs --check",
|
|
136
|
+
"docs:generate:shapes": "node scripts/generate-shapes-reference.mjs",
|
|
137
|
+
"docs:check:generated:shapes": "node scripts/generate-shapes-reference.mjs --check",
|
|
138
|
+
"shape:audit": "node scripts/shape-audit.mjs",
|
|
122
139
|
"docs:check:components": "node scripts/check-components.mjs",
|
|
123
140
|
"pack:check": "node scripts/check-pack.mjs",
|
|
124
141
|
"architecture:check": "node scripts/architecture.mjs",
|
|
@@ -126,7 +143,7 @@
|
|
|
126
143
|
"types:coverage": "node scripts/types-coverage.mjs",
|
|
127
144
|
"types:strict:check": "node scripts/types-strict.mjs",
|
|
128
145
|
"types:strict:write": "node scripts/types-strict.mjs --write",
|
|
129
|
-
"check": "npm test && npm run test:property && npm run docs:check && npm run docs:check:philosophy && npm run docs:check:generated && npm run docs:check:generated:tools && npm run docs:check:generated:config && npm run docs:check:generated:events && npm run docs:check:generated:types && npm run docs:check:generated:components && npm run docs:check:generated:locale && npm run docs:check:components && npm run surface:check && npm run pack:check && npm run architecture:check && npm run types:coverage && npm run types:strict:check && npm run types:check && npm run domain:check",
|
|
146
|
+
"check": "npm test && npm run test:property && npm run docs:check && npm run docs:check:philosophy && npm run docs:check:generated && npm run docs:check:generated:tools && npm run docs:check:generated:config && npm run docs:check:generated:events && npm run docs:check:generated:types && npm run docs:check:generated:components && npm run docs:check:generated:locale && npm run docs:check:generated:shapes && npm run shape:audit && npm run docs:check:components && npm run surface:check && npm run pack:check && npm run architecture:check && npm run types:coverage && npm run types:strict:check && npm run types:check && npm run domain:check",
|
|
130
147
|
"domain:metrics": "node scripts/domain-metrics.mjs",
|
|
131
148
|
"domain:check": "node scripts/domain-metrics.mjs --check"
|
|
132
149
|
},
|
package/ports/state.js
CHANGED
|
@@ -5,7 +5,14 @@ import { assertPort, definePort } from './contract.js'
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @typedef {object} StatePort
|
|
8
|
-
* @property {(input: {
|
|
8
|
+
* @property {(input: {
|
|
9
|
+
* store: unknown,
|
|
10
|
+
* paths: unknown,
|
|
11
|
+
* now?: number,
|
|
12
|
+
* persist?: boolean,
|
|
13
|
+
* migrations?: readonly unknown[],
|
|
14
|
+
* resolveLegacySurvey?: (rawState: Record<string, any>) => Promise<{ readCommit: string, readDirty: string[] } | null>,
|
|
15
|
+
* }) => Promise<any>} loadStateFile
|
|
9
16
|
*/
|
|
10
17
|
|
|
11
18
|
export const STATE_PORT = definePort('StatePort', ['loadStateFile'])
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$comment": "Justified architecture-gate exemptions (#80). Every entry needs rule, path, reason, and owner or ref. Scripts/architecture.mjs fails with waiver-contract when an entry is not justified. domain-pure, host-scope and no-lib-from-core cannot be waived at all, and domain/** accepts no waiver. Since #70 the retired lib/ layer has no waivers: host/** owns ambient state by rule, and only the two I/O adapters below are exempt.",
|
|
3
|
-
"waivers": [
|
|
4
|
-
{
|
|
3
|
+
"waivers": [ {
|
|
5
4
|
"rule": "child-process-surface",
|
|
6
5
|
"path": "adapters/vcs-git.js",
|
|
7
6
|
"reason": "The git adapter is the single process-spawning boundary; no other module implements VcsPort with a child process.",
|
|
@@ -15,5 +14,6 @@
|
|
|
15
14
|
"owner": "louifinn",
|
|
16
15
|
"ref": "#50"
|
|
17
16
|
}
|
|
18
|
-
]
|
|
17
|
+
],
|
|
18
|
+
"shapeWaivers": []
|
|
19
19
|
}
|
|
@@ -8,14 +8,19 @@
|
|
|
8
8
|
// locale/en.json and locale/zh.json — and the same three strings for every
|
|
9
9
|
// component that ships a generated row label, including the internal
|
|
10
10
|
// worker preset (#121);
|
|
11
|
-
// 2. a forbidden-to-disable capability (host, health, recovery,
|
|
12
|
-
//
|
|
11
|
+
// 2. a forbidden-to-disable capability (host, health, recovery, merge gates)
|
|
12
|
+
// that any group, row or boolean key can disable;
|
|
13
13
|
// 3. a component count that moved without a decision in
|
|
14
14
|
// scripts/surface-baseline.json (the same brake `surface:check` applies);
|
|
15
15
|
// 4. #137: user-facing label copy that leaks a config key, an internal tool id,
|
|
16
16
|
// a slash command or a module specifier, leaves a term untranslated in
|
|
17
17
|
// Chinese, drifts from the recorded glossary, or diverges between zh and en
|
|
18
|
-
// in the root locale, the generated row locales and the ui-bundle locale
|
|
18
|
+
// in the root locale, the generated row locales and the ui-bundle locale;
|
|
19
|
+
// 5. #M42: the review policy presented as a row. `review.mode` is one
|
|
20
|
+
// tri-state configuration control (off/optional/required) on the plugin
|
|
21
|
+
// page, not a component row and not three mutually exclusive rows; no row
|
|
22
|
+
// may own the policy, and the reviewer preset row may not be made
|
|
23
|
+
// incompatible with the off/optional modes.
|
|
19
24
|
//
|
|
20
25
|
// It also refuses the quiet forms of the same drift: a shipped component row
|
|
21
26
|
// missing from the single source of truth, a live config key without a
|
|
@@ -72,9 +77,10 @@ a visible row with no label source at all, a group row whose plugin-page
|
|
|
72
77
|
description omits its off consequence, a forbidden-to-disable capability disabled
|
|
73
78
|
by a group, row or boolean key, a component count that moved without a
|
|
74
79
|
scripts/surface-baseline.json decision, an unclassified config key or component
|
|
75
|
-
row, a stale generated reference,
|
|
76
|
-
|
|
77
|
-
|
|
80
|
+
row, a stale generated reference, (#137) any label copy that names a config key or
|
|
81
|
+
internal tool id, leaves a glossary term untranslated, or diverges between zh and
|
|
82
|
+
en, and (#M42) the review policy exposed as a row instead of one tri-state
|
|
83
|
+
configuration control.
|
|
78
84
|
Regenerate the table with \`npm run docs:generate:components\` and the row labels
|
|
79
85
|
with \`npm run docs:generate:locale\`.`
|
|
80
86
|
|
|
@@ -650,6 +656,112 @@ export function checkRowLabelSources(patch, rowLocales) {
|
|
|
650
656
|
return findings
|
|
651
657
|
}
|
|
652
658
|
|
|
659
|
+
// #M42: the review policy is one tri-state configuration control on the plugin
|
|
660
|
+
// page. These names are the policy-shaped rows the gate refuses to see: a row
|
|
661
|
+
// per mode (or one "review policy" row) is the shape the product replaced.
|
|
662
|
+
export const REVIEW_POLICY_KEY = 'review.mode'
|
|
663
|
+
export const REVIEW_ALIAS_KEY = 'review.requireReview'
|
|
664
|
+
export const REVIEW_REVIEWER_ROW = 'preset-tower-reviewer'
|
|
665
|
+
const REVIEW_POLICY_ROW_IDS = Object.freeze([
|
|
666
|
+
'review',
|
|
667
|
+
'review-policy',
|
|
668
|
+
'review-mode',
|
|
669
|
+
'review-modes',
|
|
670
|
+
'tower-review',
|
|
671
|
+
'tower-review-policy',
|
|
672
|
+
])
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* #M42: hold the review control to its declared shape. The policy is one
|
|
676
|
+
* tri-state control (`off`/`optional`/`required`) on the plugin-page
|
|
677
|
+
* configuration form, not a component row and not three mutually exclusive rows.
|
|
678
|
+
*
|
|
679
|
+
* The gate fails when a review-policy-shaped row exists, when a component
|
|
680
|
+
* `allowedOverrides` or a shipped row's `config:` block owns `review.mode` (or
|
|
681
|
+
* the deprecated alias), when the non-disableable required-mode gate is not
|
|
682
|
+
* recorded on the forbidden list, and when a forbidden capability claims the
|
|
683
|
+
* reviewer preset row as a disabler. The reviewer row is an optional capability:
|
|
684
|
+
* it may be off while the policy is off or optional, and is only a precondition
|
|
685
|
+
* (with an explanatory refusal) under required.
|
|
686
|
+
*
|
|
687
|
+
* @param {ReturnType<typeof parseComponentSurface>} surface
|
|
688
|
+
* @param {{ patch?: ReturnType<typeof parseCordisPatch> }} live
|
|
689
|
+
*/
|
|
690
|
+
export function checkReviewPolicySurface(surface, live) {
|
|
691
|
+
const findings = []
|
|
692
|
+
const record = (code, message) => findings.push(finding(code, 'error', message))
|
|
693
|
+
const components = surface?.components ?? []
|
|
694
|
+
const byName = new Map((surface?.keys ?? []).map((key) => [key.name, key]))
|
|
695
|
+
|
|
696
|
+
for (const name of [REVIEW_POLICY_KEY, REVIEW_ALIAS_KEY]) {
|
|
697
|
+
const key = byName.get(name)
|
|
698
|
+
if (!key) {
|
|
699
|
+
record('review-policy-key', `${name} is missing from the component surface; the review policy needs an explicit class`)
|
|
700
|
+
continue
|
|
701
|
+
}
|
|
702
|
+
if (key.class !== 'core') {
|
|
703
|
+
record(
|
|
704
|
+
'review-policy-class',
|
|
705
|
+
`${name} is classified "${key.class}"; the review policy is one tri-state configuration control and its keys must be core, never a switch a row can turn off`,
|
|
706
|
+
)
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
const policyRow = components.find((component) => REVIEW_POLICY_ROW_IDS.includes(component.id))
|
|
711
|
+
if (policyRow) {
|
|
712
|
+
record(
|
|
713
|
+
'review-policy-row',
|
|
714
|
+
`component "${policyRow.id}" is a review-policy row; the review policy is one tri-state control on the plugin-page configuration form, not a component row and never one row per mode`,
|
|
715
|
+
)
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
const reviewerRow = components.find((component) => component.id === REVIEW_REVIEWER_ROW)
|
|
719
|
+
if (!reviewerRow) {
|
|
720
|
+
record(
|
|
721
|
+
'review-policy-reviewer-row',
|
|
722
|
+
`${REVIEW_REVIEWER_ROW} is missing from the component surface; the reviewer preset must stay an optional capability the review policy may leave unused`,
|
|
723
|
+
)
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
for (const component of components) {
|
|
727
|
+
for (const override of component.allowedOverrides ?? []) {
|
|
728
|
+
if (override !== REVIEW_POLICY_KEY && override !== REVIEW_ALIAS_KEY) continue
|
|
729
|
+
record(
|
|
730
|
+
'review-policy-switch',
|
|
731
|
+
`component "${component.id}" lists ${override} in allowedOverrides; the review policy is one tri-state configuration control and no component row may own it`,
|
|
732
|
+
)
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
for (const row of live?.patch?.rows ?? []) {
|
|
737
|
+
for (const override of row.configOverrides ?? []) {
|
|
738
|
+
if (override.key !== REVIEW_POLICY_KEY && override.key !== REVIEW_ALIAS_KEY) continue
|
|
739
|
+
record(
|
|
740
|
+
'review-policy-switch',
|
|
741
|
+
`row "${row.id}" sets ${override.key} in cordis.patch.yml; the review policy is one tri-state configuration control, not a per-row switch`,
|
|
742
|
+
)
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
const gate = (surface?.forbidden ?? []).find((capability) => (capability.gatedBy ?? []).includes(REVIEW_POLICY_KEY))
|
|
747
|
+
if (!gate) {
|
|
748
|
+
record(
|
|
749
|
+
'review-policy-gate',
|
|
750
|
+
`no forbidden capability records the required-mode review gate via gatedBy ${REVIEW_POLICY_KEY}; required mode must keep its review evidence and exemption path non-disableable`,
|
|
751
|
+
)
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
for (const capability of surface?.forbidden ?? []) {
|
|
755
|
+
if (!(capability.rowIds ?? []).includes(REVIEW_REVIEWER_ROW)) continue
|
|
756
|
+
record(
|
|
757
|
+
'review-policy-exclusive',
|
|
758
|
+
`forbidden capability "${capability.id}" lists ${REVIEW_REVIEWER_ROW} as a row it may disable; a component may not be mutually exclusive with the review policy, which is one control, not a row`,
|
|
759
|
+
)
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
return findings
|
|
763
|
+
}
|
|
764
|
+
|
|
653
765
|
/**
|
|
654
766
|
* Run every component-surface rule against one repository root.
|
|
655
767
|
* @param {string} root
|
|
@@ -735,6 +847,8 @@ export async function checkComponents(root) {
|
|
|
735
847
|
findings.push(finding('registry-unreadable', 'error', live.registryError))
|
|
736
848
|
}
|
|
737
849
|
findings.push(...checkForbidden(surface, { booleans: live.booleans, registry: live.registry, patch: live.patch }))
|
|
850
|
+
// #M42: the review policy is one tri-state configuration control, not a row.
|
|
851
|
+
findings.push(...checkReviewPolicySurface(surface, live))
|
|
738
852
|
|
|
739
853
|
// The decision record must exist: the ADR is where the policy is argued.
|
|
740
854
|
if (!fs.existsSync(path.join(root, surface.adr))) {
|
package/scripts/check-docs.mjs
CHANGED
package/scripts/check-pack.mjs
CHANGED
|
@@ -16,6 +16,9 @@ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
|
16
16
|
const REQUIRED_FILES = [
|
|
17
17
|
'cordis.patch.yml',
|
|
18
18
|
'index.js',
|
|
19
|
+
// #M40: the core Web Client half carries the Plugins-page review policy
|
|
20
|
+
// control; it is part of the published bundle like ui-bundle/.
|
|
21
|
+
'client.js',
|
|
19
22
|
'locale/en.json',
|
|
20
23
|
'locale/zh.json',
|
|
21
24
|
// #121: every optional capability row and both preset-declaring rows explain
|
|
@@ -25,6 +28,8 @@ const REQUIRED_FILES = [
|
|
|
25
28
|
...presetLocalePaths(),
|
|
26
29
|
'host/preset-worker.js',
|
|
27
30
|
'host/preset-reviewer.js',
|
|
31
|
+
'host/preset-survey.js',
|
|
32
|
+
'survey.js',
|
|
28
33
|
'ui-bundle/index.js',
|
|
29
34
|
'ui-bundle/package.json',
|
|
30
35
|
'ui-bundle/cordis.patch.yml',
|