dsh-tower 2.1.0 → 2.3.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 +103 -0
- package/COMPATIBILITY.md +5 -1
- package/CONTRIBUTING.md +64 -5
- package/PRINCIPLES.md +145 -0
- package/README.en.md +7 -4
- package/README.md +7 -4
- package/SECURITY.md +6 -0
- package/{lib → adapters}/archive.js +1 -1
- package/{lib → adapters}/caseSensitivity.js +1 -1
- package/adapters/default.js +51 -10
- package/{lib → adapters}/git.js +1 -1
- package/{lib → adapters}/leases.js +1 -1
- package/{lib → adapters}/legacy.js +1 -1
- package/adapters/persistence.js +543 -0
- package/adapters/store-fs.js +28 -1
- package/adapters/store.js +204 -0
- package/{lib → adapters}/tests.js +9 -5
- package/adapters/vcs-git.js +15 -1
- package/{lib → adapters}/workspaceLock.js +1 -1
- package/{lib → adapters}/worktreeOwnership.js +1 -1
- package/app/archive.js +4 -6
- package/app/batch.js +9 -10
- package/app/board.js +2 -4
- package/app/checkCompletion.js +9 -10
- package/app/enrichedMissions.js +2 -4
- package/app/findings.js +6 -7
- package/app/gate.js +1 -2
- package/app/init.js +24 -29
- package/app/merge.js +58 -51
- package/app/messages.js +7 -8
- package/app/mission.js +5 -5
- package/app/plan.js +8 -5
- package/app/provision.js +12 -14
- package/app/read.js +6 -3
- package/app/rebase.js +14 -16
- package/app/recording.js +7 -8
- package/app/review.js +15 -8
- package/app/roster.js +14 -15
- package/{lib → app}/spawn-prechecks.js +4 -3
- package/app/spawn.js +12 -13
- package/app/teardown.js +8 -9
- package/app/tower.js +89 -35
- package/docs/README.md +6 -2
- package/docs/adr/0001-ports-adapters-as-data.md +49 -0
- package/docs/adr/0002-single-state-json.md +50 -0
- package/docs/adr/0003-allow-agent-control-default-true.md +48 -0
- package/docs/adr/0004-surface-budget.md +50 -0
- package/docs/adr/README.md +51 -0
- package/docs/agents/workflow.md +51 -0
- package/docs/architecture/overview.md +167 -36
- package/docs/guide/hot-reload.md +1 -1
- package/docs/guide/release.md +1 -1
- package/docs/operations/incident.md +82 -0
- package/docs/operations/runbook.md +12 -3
- package/docs/reference/blockers.md +2 -2
- package/docs/reference/config.generated.md +1 -0
- package/docs/reference/doctor.md +48 -2
- package/docs/reference/errors.generated.md +41 -37
- package/docs/reference/errors.md +53 -41
- package/docs/reference/events.md +38 -6
- package/docs/reference/surface-budget.md +128 -0
- package/docs/reference/tools.generated.md +5 -4
- package/docs/reference/tools.md +4 -4
- package/docs/zh/guide/hot-reload.md +1 -1
- package/docs/zh/reference/blockers.md +2 -2
- package/docs/zh/reference/doctor.md +29 -1
- package/docs/zh/reference/tools.md +4 -4
- package/domain/batch.js +2 -2
- package/domain/config/schema.js +25 -1
- package/domain/contracts/outputs.js +6 -2
- package/domain/contracts/tool-inputs/ops.js +1 -0
- package/domain/errors.js +297 -42
- package/domain/events.js +207 -0
- package/domain/integration/gate.js +17 -2
- package/domain/integration/merge.js +35 -0
- package/domain/lockBudget.js +30 -0
- package/domain/missions/completion.js +14 -0
- package/domain/missions/lifecycle.js +9 -3
- package/domain/missions/plan.js +14 -0
- package/domain/missions/review.js +90 -7
- package/domain/read/views.js +3 -0
- package/domain/state/archive.js +1 -1
- package/domain/state.js +1 -1
- package/domain/surface.js +248 -0
- package/{lib → host}/doctor.js +116 -9
- package/{lib → host}/modeStore.js +1 -1
- package/{lib → host}/tool-registry.js +106 -31
- package/host/tower.js +32 -0
- package/index.js +12 -9
- package/package.json +10 -3
- package/ports/archive.js +18 -0
- package/ports/caseSensitivity.js +15 -0
- package/ports/clock.js +10 -2
- package/ports/config.js +19 -0
- package/ports/contract.js +38 -0
- package/ports/events.js +24 -0
- package/ports/ids.js +10 -2
- package/ports/index.js +54 -2
- package/ports/ownership.js +17 -0
- package/ports/state.js +16 -0
- package/ports/store.js +52 -3
- package/ports/vcs.js +25 -4
- package/scripts/architecture-waivers.json +19 -0
- package/scripts/architecture.mjs +634 -23
- package/scripts/check-docs.mjs +8 -35
- package/scripts/check-pack.mjs +68 -39
- package/scripts/check-philosophy.mjs +266 -0
- package/scripts/domain-metrics.mjs +13 -4
- package/scripts/generate-config-reference.mjs +14 -1
- package/scripts/generate-error-reference.mjs +68 -14
- package/scripts/generate-events-reference.mjs +83 -0
- package/scripts/generate-tools-reference.mjs +11 -0
- package/scripts/issue-audit.mjs +2 -2
- package/scripts/lib/surface.mjs +87 -0
- package/scripts/pack-manifest.txt +58 -22
- package/scripts/regression-audit.mjs +164 -0
- package/scripts/release-check.mjs +1 -1
- package/scripts/surface-baseline.json +21 -0
- package/scripts/surface-baseline.mjs +120 -0
- package/tools/adopt.js +2 -3
- package/tools/batch.js +2 -3
- package/tools/complete.js +2 -3
- package/tools/diff.js +3 -3
- package/tools/doctor.js +7 -10
- package/tools/finding.js +2 -3
- package/tools/findings.js +3 -3
- package/tools/import-legacy.js +3 -5
- package/tools/inbox.js +3 -3
- package/tools/index.js +7 -7
- package/tools/init.js +3 -6
- package/tools/interrupt.js +2 -3
- package/tools/legacy-map.js +3 -5
- package/tools/merge.js +3 -3
- package/tools/mission.js +2 -3
- package/tools/plan.js +6 -5
- package/tools/preflight.js +2 -3
- package/tools/rebase.js +3 -3
- package/tools/recover.js +38 -13
- package/tools/reload.js +3 -6
- package/tools/resource.js +3 -5
- package/tools/review.js +3 -4
- package/tools/roster.js +2 -3
- package/tools/send.js +2 -2
- package/tools/spawn.js +8 -5
- package/tools/status.js +3 -3
- package/tools/teardown.js +2 -3
- package/tools/test.js +2 -2
- package/types/index.d.ts +1 -1
- package/ui-bundle/README.md +37 -3
- package/ui-bundle/client.cards.js +356 -0
- package/ui-bundle/client.denied.js +142 -0
- package/ui-bundle/client.dock.js +333 -0
- package/ui-bundle/client.helpers.js +271 -0
- package/ui-bundle/client.js +79 -1747
- package/ui-bundle/client.locales.js +247 -0
- package/ui-bundle/client.mode.js +206 -0
- package/ui-bundle/client.overlay.js +64 -0
- package/ui-bundle/client.styles.js +151 -0
- package/ui-bundle/client.toolview.js +82 -0
- package/ui-bundle/package.json +1 -0
- package/lib/persistence.js +0 -356
- package/lib/store.js +0 -133
- /package/{lib → adapters}/stateFile.js +0 -0
- /package/{lib → app}/briefing.js +0 -0
- /package/{lib → app}/spawn-support.js +0 -0
- /package/{lib → host}/compatibility.js +0 -0
- /package/{lib → host}/config.js +0 -0
- /package/{lib → host}/hmr.js +0 -0
- /package/{lib → host}/modeState.js +0 -0
- /package/{lib → host}/presets.js +0 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Port contract primitives (#69).
|
|
2
|
+
//
|
|
3
|
+
// A port is a runtime-checkable contract, not only a JSDoc typedef: the
|
|
4
|
+
// contract object is importable, and `assertPort` rejects a dependency object
|
|
5
|
+
// that does not implement it. That is what makes the seam substitutable at
|
|
6
|
+
// runtime instead of merely documented.
|
|
7
|
+
export function definePort(name, methods) {
|
|
8
|
+
return Object.freeze({ name, methods: Object.freeze([...methods]) })
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {unknown} impl
|
|
13
|
+
* @param {{ name: string, methods: readonly string[] }} contract
|
|
14
|
+
* @param {string} [label]
|
|
15
|
+
*/
|
|
16
|
+
export function assertPort(impl, contract, label = contract.name) {
|
|
17
|
+
if (!impl || typeof impl !== 'object') {
|
|
18
|
+
throw new TypeError(`${label}: expected an object implementing ${contract.name}`)
|
|
19
|
+
}
|
|
20
|
+
const missing = contract.methods.filter((method) => typeof (/** @type {any} */ (impl))[method] !== 'function')
|
|
21
|
+
if (missing.length > 0) {
|
|
22
|
+
throw new TypeError(`${label} does not implement ${contract.name}; missing: ${missing.join(', ')}`)
|
|
23
|
+
}
|
|
24
|
+
return impl
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {unknown} impl
|
|
29
|
+
* @param {{ name: string, methods: readonly string[] }} contract
|
|
30
|
+
*/
|
|
31
|
+
export function implementsPort(impl, contract) {
|
|
32
|
+
try {
|
|
33
|
+
assertPort(impl, contract)
|
|
34
|
+
return true
|
|
35
|
+
} catch {
|
|
36
|
+
return false
|
|
37
|
+
}
|
|
38
|
+
}
|
package/ports/events.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Domain-event port (#75).
|
|
2
|
+
//
|
|
3
|
+
// The app layer persists a transition and then emits the domain events it
|
|
4
|
+
// described through this port. The host binds it to Cordis `ctx.emit`; tests
|
|
5
|
+
// bind it to a recorder. `emit` is best effort by contract: a listener (or a
|
|
6
|
+
// missing service) must never fail the tool that produced the fact.
|
|
7
|
+
import { assertPort, definePort } from './contract.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {object} EventPort
|
|
11
|
+
* @property {(name: string, payload: Record<string, any>) => void} emit
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export const EVENT_PORT = definePort('EventPort', ['emit'])
|
|
15
|
+
|
|
16
|
+
/** @param {unknown} impl */
|
|
17
|
+
export function assertEventPort(impl) {
|
|
18
|
+
return /** @type {EventPort} */ (assertPort(impl, EVENT_PORT, 'events'))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The no-op default: embedders without an event channel still get a Tower. */
|
|
22
|
+
export const NOOP_EVENT_PORT = Object.freeze({
|
|
23
|
+
emit() {},
|
|
24
|
+
})
|
package/ports/ids.js
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
// Ids port (#
|
|
1
|
+
// Ids port (#69).
|
|
2
|
+
import { assertPort, definePort } from './contract.js'
|
|
3
|
+
|
|
2
4
|
/**
|
|
3
5
|
* @typedef {object} IdsPort
|
|
4
6
|
* @property {() => string} uuid
|
|
5
7
|
* @property {(text: string) => string} shortHash
|
|
6
8
|
*/
|
|
7
|
-
|
|
9
|
+
|
|
10
|
+
export const IDS_PORT = definePort('IdsPort', ['uuid', 'shortHash'])
|
|
11
|
+
|
|
12
|
+
/** @param {unknown} impl */
|
|
13
|
+
export function assertIdsPort(impl) {
|
|
14
|
+
return /** @type {IdsPort} */ (assertPort(impl, IDS_PORT, 'ids'))
|
|
15
|
+
}
|
package/ports/index.js
CHANGED
|
@@ -1,2 +1,54 @@
|
|
|
1
|
-
// Explicit ports consumed by app use cases (#
|
|
2
|
-
|
|
1
|
+
// Explicit ports consumed by app use cases (#69).
|
|
2
|
+
//
|
|
3
|
+
// `assertPorts` is the injection check the app layer runs on the dependency
|
|
4
|
+
// object it receives: a composition root that forgets a port fails at
|
|
5
|
+
// construction, not in the middle of a mission.
|
|
6
|
+
import { assertPort } from './contract.js'
|
|
7
|
+
import { STORE_PORT, assertStorePort } from './store.js'
|
|
8
|
+
import { VCS_PORT, assertVcsPort } from './vcs.js'
|
|
9
|
+
import { CLOCK_PORT, assertClockPort } from './clock.js'
|
|
10
|
+
import { IDS_PORT, assertIdsPort } from './ids.js'
|
|
11
|
+
import { CONFIG_PORT, assertConfigPort } from './config.js'
|
|
12
|
+
import { STATE_PORT, assertStatePort } from './state.js'
|
|
13
|
+
import { OWNERSHIP_PORT, assertOwnershipPort } from './ownership.js'
|
|
14
|
+
import { CASE_SENSITIVITY_PORT, assertCaseSensitivityPort } from './caseSensitivity.js'
|
|
15
|
+
import { ARCHIVE_PORT, assertArchivePort } from './archive.js'
|
|
16
|
+
import { EVENT_PORT, assertEventPort } from './events.js'
|
|
17
|
+
|
|
18
|
+
/** Every port the Tower app layer depends on, keyed by deps member. */
|
|
19
|
+
export const PORTS = Object.freeze({
|
|
20
|
+
store: { contract: STORE_PORT, assert: assertStorePort },
|
|
21
|
+
vcs: { contract: VCS_PORT, assert: assertVcsPort },
|
|
22
|
+
clock: { contract: CLOCK_PORT, assert: assertClockPort },
|
|
23
|
+
ids: { contract: IDS_PORT, assert: assertIdsPort },
|
|
24
|
+
config: { contract: CONFIG_PORT, assert: assertConfigPort },
|
|
25
|
+
state: { contract: STATE_PORT, assert: assertStatePort },
|
|
26
|
+
ownership: { contract: OWNERSHIP_PORT, assert: assertOwnershipPort },
|
|
27
|
+
caseSensitivity: { contract: CASE_SENSITIVITY_PORT, assert: assertCaseSensitivityPort },
|
|
28
|
+
archive: { contract: ARCHIVE_PORT, assert: assertArchivePort },
|
|
29
|
+
events: { contract: EVENT_PORT, assert: assertEventPort },
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
/** @param {unknown} deps */
|
|
33
|
+
export function assertPorts(deps) {
|
|
34
|
+
if (!deps || typeof deps !== 'object') {
|
|
35
|
+
throw new TypeError('Tower requires an injected deps object assembled by the composition root (see adapters/default.js)')
|
|
36
|
+
}
|
|
37
|
+
for (const [name, port] of Object.entries(PORTS)) {
|
|
38
|
+
if (typeof port.assert === 'function') port.assert(deps[name])
|
|
39
|
+
else assertPort(deps[name], port.contract, name)
|
|
40
|
+
}
|
|
41
|
+
return deps
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export { STORE_PORT, assertStorePort } from './store.js'
|
|
45
|
+
export { VCS_PORT, assertVcsPort } from './vcs.js'
|
|
46
|
+
export { CLOCK_PORT, assertClockPort } from './clock.js'
|
|
47
|
+
export { IDS_PORT, assertIdsPort } from './ids.js'
|
|
48
|
+
export { CONFIG_PORT, assertConfigPort } from './config.js'
|
|
49
|
+
export { STATE_PORT, assertStatePort } from './state.js'
|
|
50
|
+
export { OWNERSHIP_PORT, assertOwnershipPort } from './ownership.js'
|
|
51
|
+
export { CASE_SENSITIVITY_PORT, assertCaseSensitivityPort } from './caseSensitivity.js'
|
|
52
|
+
export { ARCHIVE_PORT, assertArchivePort } from './archive.js'
|
|
53
|
+
export { EVENT_PORT, assertEventPort, NOOP_EVENT_PORT } from './events.js'
|
|
54
|
+
export { definePort, assertPort, implementsPort } from './contract.js'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Worktree-ownership port (#69). The default implementation verifies a stored
|
|
2
|
+
// worktree against the real filesystem and Git; an in-memory composition can
|
|
3
|
+
// substitute a verifier that matches its own virtual paths.
|
|
4
|
+
import { assertPort, definePort } from './contract.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {object} OwnershipPort
|
|
8
|
+
* @property {(input: { storedPath: string, storedRealPath?: string, branch: string, commonDir: string, missionId?: string }) => Promise<{ path: string, realPath: string }>} assertOwnership
|
|
9
|
+
* @property {(target: string) => Promise<string>} canonicalPath
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export const OWNERSHIP_PORT = definePort('OwnershipPort', ['assertOwnership', 'canonicalPath'])
|
|
13
|
+
|
|
14
|
+
/** @param {unknown} impl */
|
|
15
|
+
export function assertOwnershipPort(impl) {
|
|
16
|
+
return /** @type {OwnershipPort} */ (assertPort(impl, OWNERSHIP_PORT, 'ownership'))
|
|
17
|
+
}
|
package/ports/state.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// State-load port (#69): schema inspection, migration, and the one persisted
|
|
2
|
+
// migration result. The implementation is store-agnostic and receives the
|
|
3
|
+
// store port, so an in-memory store can drive the same load path.
|
|
4
|
+
import { assertPort, definePort } from './contract.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {object} StatePort
|
|
8
|
+
* @property {(input: { store: unknown, paths: unknown, now?: number, persist?: boolean, migrations?: readonly unknown[] }) => Promise<any>} loadStateFile
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const STATE_PORT = definePort('StatePort', ['loadStateFile'])
|
|
12
|
+
|
|
13
|
+
/** @param {unknown} impl */
|
|
14
|
+
export function assertStatePort(impl) {
|
|
15
|
+
return /** @type {StatePort} */ (assertPort(impl, STATE_PORT, 'state'))
|
|
16
|
+
}
|
package/ports/store.js
CHANGED
|
@@ -1,11 +1,60 @@
|
|
|
1
|
-
// Store port (#
|
|
1
|
+
// Store port (#69). Implementation: adapters/store-fs.js.
|
|
2
|
+
//
|
|
3
|
+
// App use cases depend on this contract, not on adapters/store.js: they receive the
|
|
4
|
+
// implementation through the composition root and never import the adapter.
|
|
5
|
+
// The contract lists every method app/** and tools/** call, so an implementation
|
|
6
|
+
// that is missing one fails `assertPorts` at construction instead of mid-mission.
|
|
7
|
+
import { assertPort, definePort } from './contract.js'
|
|
8
|
+
|
|
2
9
|
/**
|
|
3
10
|
* @typedef {object} StorePort
|
|
11
|
+
* @property {(commonDir: string) => { dir: string, state: string, lock: string, gitLock: string, activity: string }} pathsFor
|
|
4
12
|
* @property {(file: string) => Promise<boolean>} exists
|
|
5
13
|
* @property {(dir: string) => Promise<void>} ensureDir
|
|
6
14
|
* @property {(file: string) => Promise<any>} readJson
|
|
7
15
|
* @property {(file: string, value: unknown) => Promise<void>} writeJsonAtomic
|
|
8
16
|
* @property {(file: string, value: unknown) => Promise<void>} appendJsonl
|
|
9
|
-
* @property {(paths: object, fn: () => Promise<any>, options?: object) => Promise<any>} withLock
|
|
17
|
+
* @property {(paths: object, fn: (paths: object) => Promise<any>, options?: object) => Promise<any>} withLock
|
|
18
|
+
* @property {(paths: object, fn: (paths: object) => Promise<any>, options?: object) => Promise<any>} withGitLock
|
|
19
|
+
* @property {(file: string, limit?: number) => Promise<any[]>} readActivityTail
|
|
20
|
+
* @property {(file: string) => Promise<string>} readText
|
|
21
|
+
* @property {(file: string, text: string, encoding?: string) => Promise<void>} writeText
|
|
22
|
+
* @property {(file: string, text: string, encoding?: string) => Promise<void>} appendText
|
|
23
|
+
* @property {(from: string, to: string) => Promise<void>} rename
|
|
24
|
+
* @property {(from: string, to: string) => Promise<void>} renameWithRetry
|
|
25
|
+
* @property {(file: string) => Promise<{ mtimeMs: number }>} stat
|
|
26
|
+
* @property {(file: string) => Promise<void>} unlink
|
|
27
|
+
* @property {(paths: object, options?: object) => Promise<{ record: any, raw: string | null }>} readGitLock
|
|
28
|
+
* @property {(lock: { record: any } | null, options?: object) => { held: boolean, pid?: number | null, phase?: string | null, heldMs?: number }} gitLockView
|
|
10
29
|
*/
|
|
11
|
-
|
|
30
|
+
|
|
31
|
+
// `rename` and `renameWithRetry` are both declared because they are not
|
|
32
|
+
// interchangeable: `renameWithRetry` has temp-file semantics and on a terminal
|
|
33
|
+
// failure it unlinks its source, which is right for a disposable temp file but
|
|
34
|
+
// would delete the bytes `tower_recover` must keep auditable. The audit rename
|
|
35
|
+
// therefore uses the plain `rename` primitive (see tools/recover.js).
|
|
36
|
+
export const STORE_PORT = definePort('StorePort', [
|
|
37
|
+
'pathsFor',
|
|
38
|
+
'exists',
|
|
39
|
+
'ensureDir',
|
|
40
|
+
'readJson',
|
|
41
|
+
'writeJsonAtomic',
|
|
42
|
+
'appendJsonl',
|
|
43
|
+
'withLock',
|
|
44
|
+
'withGitLock',
|
|
45
|
+
'readActivityTail',
|
|
46
|
+
'readText',
|
|
47
|
+
'writeText',
|
|
48
|
+
'appendText',
|
|
49
|
+
'rename',
|
|
50
|
+
'renameWithRetry',
|
|
51
|
+
'stat',
|
|
52
|
+
'unlink',
|
|
53
|
+
'readGitLock',
|
|
54
|
+
'gitLockView',
|
|
55
|
+
])
|
|
56
|
+
|
|
57
|
+
/** @param {unknown} impl */
|
|
58
|
+
export function assertStorePort(impl) {
|
|
59
|
+
return /** @type {StorePort} */ (assertPort(impl, STORE_PORT, 'store'))
|
|
60
|
+
}
|
package/ports/vcs.js
CHANGED
|
@@ -1,11 +1,32 @@
|
|
|
1
|
-
// Vcs port (#
|
|
1
|
+
// Vcs port (#69). Implementation: adapters/vcs-git.js.
|
|
2
|
+
import { assertPort, definePort } from './contract.js'
|
|
3
|
+
|
|
2
4
|
/**
|
|
3
5
|
* @typedef {object} VcsPort
|
|
4
6
|
* @property {(cwd: string, args: string[], options?: object) => Promise<string>} git
|
|
5
7
|
* @property {(cwd: string, args: string[], options?: object) => Promise<string|null>} tryGit
|
|
6
8
|
* @property {(cwd: string, ref: string) => Promise<string>} revParse
|
|
7
9
|
* @property {(cwd: string) => Promise<boolean>} isClean
|
|
8
|
-
* @property {(cwd: string,
|
|
9
|
-
* @property {(repoRoot: string) => Promise<{root:string, commonDir:string, branch:string|null}>} resolveRepo
|
|
10
|
+
* @property {(cwd: string, ancestor: string, descendant: string) => Promise<boolean>} isAncestor
|
|
11
|
+
* @property {(repoRoot: string) => Promise<{ root: string, commonDir: string, branch: string|null }>} resolveRepo
|
|
12
|
+
* @property {(cwd: string) => Promise<string|null>} currentBranch
|
|
13
|
+
* @property {(cwd: string, fromRef: string) => Promise<number>} countCommits
|
|
14
|
+
* @property {(cwd: string) => Promise<Array<{ path: string, branch?: string, head?: string }>>} listWorktrees
|
|
10
15
|
*/
|
|
11
|
-
|
|
16
|
+
|
|
17
|
+
export const VCS_PORT = definePort('VcsPort', [
|
|
18
|
+
'git',
|
|
19
|
+
'tryGit',
|
|
20
|
+
'revParse',
|
|
21
|
+
'isClean',
|
|
22
|
+
'isAncestor',
|
|
23
|
+
'resolveRepo',
|
|
24
|
+
'currentBranch',
|
|
25
|
+
'countCommits',
|
|
26
|
+
'listWorktrees',
|
|
27
|
+
])
|
|
28
|
+
|
|
29
|
+
/** @param {unknown} impl */
|
|
30
|
+
export function assertVcsPort(impl) {
|
|
31
|
+
return /** @type {VcsPort} */ (assertPort(impl, VCS_PORT, 'vcs'))
|
|
32
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
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
|
+
{
|
|
5
|
+
"rule": "child-process-surface",
|
|
6
|
+
"path": "adapters/vcs-git.js",
|
|
7
|
+
"reason": "The git adapter is the single process-spawning boundary; no other module implements VcsPort with a child process.",
|
|
8
|
+
"owner": "louifinn",
|
|
9
|
+
"ref": "#50"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"rule": "fs-surface",
|
|
13
|
+
"path": "adapters/store-fs.js",
|
|
14
|
+
"reason": "The filesystem adapter is the single node:fs boundary; the store port needs atomic writes, lock files, and raw store reads.",
|
|
15
|
+
"owner": "louifinn",
|
|
16
|
+
"ref": "#50"
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|