@aperant/framework 0.10.0 → 0.12.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 +236 -1
- package/agents/apt-planner.md +15 -0
- package/agents/apt-verifier.md +15 -10
- package/bin/apt-proof-video.mjs +256 -76
- package/dist/cli/commands/apply-recipe.d.mts +6 -0
- package/dist/cli/commands/apply-recipe.d.mts.map +1 -0
- package/dist/cli/commands/apply-recipe.mjs +98 -0
- package/dist/cli/commands/apply-recipe.mjs.map +1 -0
- package/dist/cli/commands/catch-up.d.mts +9 -0
- package/dist/cli/commands/catch-up.d.mts.map +1 -0
- package/dist/cli/commands/catch-up.mjs +64 -0
- package/dist/cli/commands/catch-up.mjs.map +1 -0
- package/dist/cli/commands/mobile-prepare.d.mts +53 -0
- package/dist/cli/commands/mobile-prepare.d.mts.map +1 -0
- package/dist/cli/commands/mobile-prepare.mjs +139 -0
- package/dist/cli/commands/mobile-prepare.mjs.map +1 -0
- package/dist/cli/commands/pr-review.d.mts +16 -4
- package/dist/cli/commands/pr-review.d.mts.map +1 -1
- package/dist/cli/commands/pr-review.mjs +30 -11
- package/dist/cli/commands/pr-review.mjs.map +1 -1
- package/dist/cli/commands/recipe-diff.d.mts +6 -0
- package/dist/cli/commands/recipe-diff.d.mts.map +1 -0
- package/dist/cli/commands/recipe-diff.mjs +75 -0
- package/dist/cli/commands/recipe-diff.mjs.map +1 -0
- package/dist/cli/commands/roadmap.d.mts.map +1 -1
- package/dist/cli/commands/roadmap.mjs +53 -0
- package/dist/cli/commands/roadmap.mjs.map +1 -1
- package/dist/cli/commands/route.d.mts.map +1 -1
- package/dist/cli/commands/route.mjs +92 -2
- package/dist/cli/commands/route.mjs.map +1 -1
- package/dist/cli/commands/verify-sandbox.d.mts +6 -0
- package/dist/cli/commands/verify-sandbox.d.mts.map +1 -0
- package/dist/cli/commands/verify-sandbox.mjs +200 -0
- package/dist/cli/commands/verify-sandbox.mjs.map +1 -0
- package/dist/cli/config/share-policy.d.mts.map +1 -1
- package/dist/cli/config/share-policy.mjs +1 -0
- package/dist/cli/config/share-policy.mjs.map +1 -1
- package/dist/cli/config/upgrade-gitignore.d.mts.map +1 -1
- package/dist/cli/config/upgrade-gitignore.mjs +1 -0
- package/dist/cli/config/upgrade-gitignore.mjs.map +1 -1
- package/dist/cli/consistency/parse-qa.d.mts.map +1 -1
- package/dist/cli/consistency/parse-qa.mjs +18 -4
- package/dist/cli/consistency/parse-qa.mjs.map +1 -1
- package/dist/cli/consistency/rules/r5-verdict-consistency.d.mts.map +1 -1
- package/dist/cli/consistency/rules/r5-verdict-consistency.mjs +11 -10
- package/dist/cli/consistency/rules/r5-verdict-consistency.mjs.map +1 -1
- package/dist/cli/coordination/catch-up.d.mts +26 -0
- package/dist/cli/coordination/catch-up.d.mts.map +1 -0
- package/dist/cli/coordination/catch-up.mjs +239 -0
- package/dist/cli/coordination/catch-up.mjs.map +1 -0
- package/dist/cli/coordination/last-seen.d.mts +45 -0
- package/dist/cli/coordination/last-seen.d.mts.map +1 -0
- package/dist/cli/coordination/last-seen.mjs +128 -0
- package/dist/cli/coordination/last-seen.mjs.map +1 -0
- package/dist/cli/coordination/store.d.mts +15 -0
- package/dist/cli/coordination/store.d.mts.map +1 -1
- package/dist/cli/coordination/store.mjs +16 -0
- package/dist/cli/coordination/store.mjs.map +1 -1
- package/dist/cli/coverage-check/user-outcomes.d.mts +7 -2
- package/dist/cli/coverage-check/user-outcomes.d.mts.map +1 -1
- package/dist/cli/coverage-check/user-outcomes.mjs +50 -17
- package/dist/cli/coverage-check/user-outcomes.mjs.map +1 -1
- package/dist/cli/dispatch.d.mts.map +1 -1
- package/dist/cli/dispatch.mjs +30 -0
- package/dist/cli/dispatch.mjs.map +1 -1
- package/dist/cli/gate/gates/verify-approved.d.mts +0 -48
- package/dist/cli/gate/gates/verify-approved.d.mts.map +1 -1
- package/dist/cli/gate/gates/verify-approved.mjs +23 -80
- package/dist/cli/gate/gates/verify-approved.mjs.map +1 -1
- package/dist/cli/git/default-branch.d.mts +51 -0
- package/dist/cli/git/default-branch.d.mts.map +1 -0
- package/dist/cli/git/default-branch.mjs +234 -0
- package/dist/cli/git/default-branch.mjs.map +1 -0
- package/dist/cli/git/identity.d.mts +3 -5
- package/dist/cli/git/identity.d.mts.map +1 -1
- package/dist/cli/git/identity.mjs +10 -4
- package/dist/cli/git/identity.mjs.map +1 -1
- package/dist/cli/install/legacy-paths.d.mts.map +1 -1
- package/dist/cli/install/legacy-paths.mjs +2 -0
- package/dist/cli/install/legacy-paths.mjs.map +1 -1
- package/dist/cli/install/mcp-server-specs.d.mts +6 -5
- package/dist/cli/install/mcp-server-specs.d.mts.map +1 -1
- package/dist/cli/install/mcp-server-specs.mjs +22 -4
- package/dist/cli/install/mcp-server-specs.mjs.map +1 -1
- package/dist/cli/roadmap/backend-adapter.d.mts +43 -0
- package/dist/cli/roadmap/backend-adapter.d.mts.map +1 -0
- package/dist/cli/roadmap/backend-adapter.mjs +222 -0
- package/dist/cli/roadmap/backend-adapter.mjs.map +1 -0
- package/dist/cli/util/runtime-capabilities.d.mts +7 -0
- package/dist/cli/util/runtime-capabilities.d.mts.map +1 -1
- package/dist/cli/util/runtime-capabilities.mjs +65 -0
- package/dist/cli/util/runtime-capabilities.mjs.map +1 -1
- package/dist/cli/verify-proof/manifest-validator.mjs +13 -0
- package/dist/cli/verify-proof/manifest-validator.mjs.map +1 -1
- package/dist/cli/verify-proof/resolver.d.mts +10 -7
- package/dist/cli/verify-proof/resolver.d.mts.map +1 -1
- package/dist/cli/verify-proof/resolver.mjs +28 -7
- package/dist/cli/verify-proof/resolver.mjs.map +1 -1
- package/dist/cli/verify-proof/sandbox/apply.d.mts +64 -0
- package/dist/cli/verify-proof/sandbox/apply.d.mts.map +1 -0
- package/dist/cli/verify-proof/sandbox/apply.mjs +379 -0
- package/dist/cli/verify-proof/sandbox/apply.mjs.map +1 -0
- package/dist/cli/verify-proof/sandbox/gate-predicate.d.mts +63 -0
- package/dist/cli/verify-proof/sandbox/gate-predicate.d.mts.map +1 -0
- package/dist/cli/verify-proof/sandbox/gate-predicate.mjs +115 -0
- package/dist/cli/verify-proof/sandbox/gate-predicate.mjs.map +1 -0
- package/dist/cli/verify-proof/sandbox/pattern-matcher.d.mts +57 -0
- package/dist/cli/verify-proof/sandbox/pattern-matcher.d.mts.map +1 -0
- package/dist/cli/verify-proof/sandbox/pattern-matcher.mjs +124 -0
- package/dist/cli/verify-proof/sandbox/pattern-matcher.mjs.map +1 -0
- package/dist/cli/verify-proof/sandbox/recipe-diff.d.mts +28 -0
- package/dist/cli/verify-proof/sandbox/recipe-diff.d.mts.map +1 -0
- package/dist/cli/verify-proof/sandbox/recipe-diff.mjs +190 -0
- package/dist/cli/verify-proof/sandbox/recipe-diff.mjs.map +1 -0
- package/dist/cli/verify-proof/sandbox/sandbox-config.d.mts +25 -0
- package/dist/cli/verify-proof/sandbox/sandbox-config.d.mts.map +1 -0
- package/dist/cli/verify-proof/sandbox/sandbox-config.mjs +85 -0
- package/dist/cli/verify-proof/sandbox/sandbox-config.mjs.map +1 -0
- package/dist/cli/verify-proof/sandbox/self-test.d.mts +39 -0
- package/dist/cli/verify-proof/sandbox/self-test.d.mts.map +1 -0
- package/dist/cli/verify-proof/sandbox/self-test.mjs +248 -0
- package/dist/cli/verify-proof/sandbox/self-test.mjs.map +1 -0
- package/dist/plugin/.claude-plugin/plugin.json +2 -1
- package/dist/plugin/agents/apt-planner.md +15 -0
- package/dist/plugin/agents/apt-verifier.md +15 -10
- package/dist/plugin/skills/apt/SKILL.md +29 -0
- package/dist/plugin/skills/apt-catch-up/SKILL.md +79 -0
- package/dist/plugin/skills/apt-plan/SKILL.md +23 -0
- package/dist/plugin/skills/apt-pr-review/SKILL.md +5 -4
- package/dist/plugin/skills/apt-run/SKILL.md +1 -1
- package/dist/plugin/skills/apt-setup/SKILL.md +132 -0
- package/dist/plugin/skills/apt-verify/SKILL.md +20 -15
- package/dist/plugin/skills/apt-verify-proof/SKILL.md +146 -11
- package/dist/proof-report.d.ts.map +1 -1
- package/dist/proof-report.js +4 -1
- package/dist/proof-report.js.map +1 -1
- package/dist/types/config.d.ts +42 -4
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/qa-scoring.d.ts +45 -9
- package/dist/types/qa-scoring.d.ts.map +1 -1
- package/dist/types/qa-scoring.js +34 -13
- package/dist/types/qa-scoring.js.map +1 -1
- package/dist/types/state.d.ts +7 -2
- package/dist/types/state.d.ts.map +1 -1
- package/dist/types/task-record.d.ts +10 -2
- package/dist/types/task-record.d.ts.map +1 -1
- package/drivers/mobile/README.md +40 -0
- package/drivers/mobile/driver.mjs +106 -0
- package/drivers/mobile/manifest.json +49 -0
- package/package.json +1 -1
- package/prompts/inbox-clarification.md +11 -0
- package/prompts/inbox-triage.md +20 -0
- package/prompts/qa_orchestrator_agentic.md +18 -17
- package/prompts/qa_reviewer.md +17 -15
- package/skills/apt/SKILL.md +29 -0
- package/skills/apt-catch-up/SKILL.md +79 -0
- package/skills/apt-plan/SKILL.md +23 -0
- package/skills/apt-pr-review/SKILL.md +5 -4
- package/skills/apt-run/SKILL.md +1 -1
- package/skills/apt-setup/SKILL.md +132 -0
- package/skills/apt-verifier.md +7 -4
- package/skills/apt-verify/SKILL.md +20 -15
- package/skills/apt-verify-proof/SKILL.md +146 -11
- package/src/cli/commands/apply-recipe.mjs +105 -0
- package/src/cli/commands/catch-up.mjs +67 -0
- package/src/cli/commands/mobile-prepare.mjs +151 -0
- package/src/cli/commands/pr-review.mjs +32 -11
- package/src/cli/commands/recipe-diff.mjs +81 -0
- package/src/cli/commands/roadmap.mjs +54 -0
- package/src/cli/commands/route.mjs +92 -1
- package/src/cli/commands/verify-sandbox.mjs +231 -0
- package/src/cli/config/share-policy.mjs +1 -0
- package/src/cli/config/upgrade-gitignore.mjs +1 -0
- package/src/cli/consistency/parse-qa.mjs +20 -4
- package/src/cli/consistency/rules/r5-verdict-consistency.mjs +11 -13
- package/src/cli/coordination/catch-up.mjs +231 -0
- package/src/cli/coordination/last-seen.mjs +131 -0
- package/src/cli/coordination/store.mjs +18 -0
- package/src/cli/coverage-check/user-outcomes.mjs +52 -17
- package/src/cli/dispatch.mjs +29 -0
- package/src/cli/gate/gates/verify-approved.mjs +22 -81
- package/src/cli/git/default-branch.mjs +250 -0
- package/src/cli/git/identity.mjs +9 -3
- package/src/cli/install/legacy-paths.mjs +2 -0
- package/src/cli/install/mcp-server-specs.mjs +24 -4
- package/src/cli/roadmap/backend-adapter.mjs +231 -0
- package/src/cli/util/runtime-capabilities.mjs +67 -0
- package/src/cli/verify-proof/manifest-validator.mjs +15 -0
- package/src/cli/verify-proof/resolver.mjs +27 -7
- package/src/cli/verify-proof/sandbox/apply.mjs +401 -0
- package/src/cli/verify-proof/sandbox/gate-predicate.mjs +126 -0
- package/src/cli/verify-proof/sandbox/pattern-matcher.mjs +127 -0
- package/src/cli/verify-proof/sandbox/recipe-diff.mjs +208 -0
- package/src/cli/verify-proof/sandbox/sandbox-config.mjs +82 -0
- package/src/cli/verify-proof/sandbox/sandbox-schema.json +33 -0
- package/src/cli/verify-proof/sandbox/self-test.mjs +265 -0
- package/templates/config.json +21 -1
- package/templates/proof-verification.md +27 -8
- package/workflows/verify-proof.md +376 -302
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @internal
|
|
3
|
+
* coordination/catch-up.mjs — composes the combined catch-up panel: the
|
|
4
|
+
* Welcome-Back Summary (your own half-finished work, local-only) and the
|
|
5
|
+
* gated Team-Change Digest (what teammates merged while you were away).
|
|
6
|
+
*
|
|
7
|
+
* Composition order is fixed: welcome_back first, then team_digest (LD-10).
|
|
8
|
+
* The renderer (apt/SKILL.md) enforces display order; this module only sets
|
|
9
|
+
* the field order on the returned object.
|
|
10
|
+
*
|
|
11
|
+
* Both halves diff against one Last-Seen Marker:
|
|
12
|
+
* - Welcome-Back fires on a staleness threshold (now - last_run_at), reads
|
|
13
|
+
* only local state.active_tasks, and is ALWAYS-ON regardless of visibility
|
|
14
|
+
* (LD-08/09).
|
|
15
|
+
* - Team-Change Digest fires only when share.visibility is team/oss AND the
|
|
16
|
+
* local default-branch sha has moved since last_main_sha (LD-04/06/07).
|
|
17
|
+
*
|
|
18
|
+
* Returns null when neither half fires. This module is `@internal` — reached
|
|
19
|
+
* via the route.mjs lazy-compute path and the apt-tools catch-up command, not
|
|
20
|
+
* from bin/apt-tools.mjs directly.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
24
|
+
import { join } from 'node:path'
|
|
25
|
+
import { resolveSharePolicy } from '../config/share-policy.mjs'
|
|
26
|
+
import { listMergedPullRequests } from '../git/default-branch.mjs'
|
|
27
|
+
import { getGitIdentity } from '../git/identity.mjs'
|
|
28
|
+
import { resolveSharedRoot } from './agent-identity.mjs'
|
|
29
|
+
|
|
30
|
+
const HOUR_MS = 60 * 60 * 1000
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Best-available per-task activity timestamp (ms). Prefers an explicit
|
|
34
|
+
* updated_at, then the most recent history entry, then created_at.
|
|
35
|
+
*
|
|
36
|
+
* @param {object} task
|
|
37
|
+
* @returns {number} epoch ms, or 0 when no timestamp is parseable.
|
|
38
|
+
*/
|
|
39
|
+
function taskActivityMs(task) {
|
|
40
|
+
const candidates = []
|
|
41
|
+
if (task?.updated_at) candidates.push(Date.parse(task.updated_at))
|
|
42
|
+
if (Array.isArray(task?.history)) {
|
|
43
|
+
for (const h of task.history) {
|
|
44
|
+
if (h?.at) candidates.push(Date.parse(h.at))
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (task?.created_at) candidates.push(Date.parse(task.created_at))
|
|
48
|
+
const valid = candidates.filter((n) => Number.isFinite(n))
|
|
49
|
+
return valid.length ? Math.max(...valid) : 0
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* A coarse "x ago" relative-time string. Kept dependency-free and stable for
|
|
54
|
+
* tests — granularity is minutes/hours/days.
|
|
55
|
+
*
|
|
56
|
+
* @param {number} deltaMs
|
|
57
|
+
* @returns {string}
|
|
58
|
+
*/
|
|
59
|
+
function relativeTime(deltaMs) {
|
|
60
|
+
if (!Number.isFinite(deltaMs) || deltaMs < 0) return 'just now'
|
|
61
|
+
const mins = Math.floor(deltaMs / 60000)
|
|
62
|
+
if (mins < 1) return 'just now'
|
|
63
|
+
if (mins < 60) return `${mins}m ago`
|
|
64
|
+
const hours = Math.floor(mins / 60)
|
|
65
|
+
if (hours < 24) return `${hours}h ago`
|
|
66
|
+
const days = Math.floor(hours / 24)
|
|
67
|
+
return `${days}d ago`
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Read the first actionable next-step line from a task's CONTINUE note, if one
|
|
72
|
+
* exists at `.aperant/tasks/{taskId}/CONTINUE.md`. Parses the first non-empty
|
|
73
|
+
* item under the `## Next Steps` heading (the pause-note format).
|
|
74
|
+
*
|
|
75
|
+
* @param {string} sharedRoot
|
|
76
|
+
* @param {string} taskId
|
|
77
|
+
* @returns {string|null}
|
|
78
|
+
*/
|
|
79
|
+
function readNextStepLine(sharedRoot, taskId) {
|
|
80
|
+
if (!taskId) return null
|
|
81
|
+
const notePath = join(sharedRoot, '.aperant', 'tasks', taskId, 'CONTINUE.md')
|
|
82
|
+
if (!existsSync(notePath)) return null
|
|
83
|
+
let text
|
|
84
|
+
try {
|
|
85
|
+
text = readFileSync(notePath, 'utf-8')
|
|
86
|
+
} catch {
|
|
87
|
+
return null
|
|
88
|
+
}
|
|
89
|
+
const lines = text.split('\n')
|
|
90
|
+
const headingIdx = lines.findIndex((l) => /^##\s+Next Steps\s*$/i.test(l.trim()))
|
|
91
|
+
if (headingIdx === -1) return null
|
|
92
|
+
for (let i = headingIdx + 1; i < lines.length; i++) {
|
|
93
|
+
const raw = lines[i].trim()
|
|
94
|
+
if (raw.startsWith('##')) break // next section
|
|
95
|
+
if (raw.length === 0) continue
|
|
96
|
+
// Strip a leading list marker: "1. ", "- ", "* ".
|
|
97
|
+
const cleaned = raw.replace(/^(\d+\.|[-*])\s+/, '').trim()
|
|
98
|
+
if (cleaned.length > 0) return cleaned
|
|
99
|
+
}
|
|
100
|
+
return null
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Build the Welcome-Back half from local state (LD-08/09). Returns null when
|
|
105
|
+
* the threshold has not elapsed or there is no active task.
|
|
106
|
+
*
|
|
107
|
+
* @param {object} params
|
|
108
|
+
* @param {object} params.config
|
|
109
|
+
* @param {number} params.now epoch ms
|
|
110
|
+
* @param {object|null} params.marker
|
|
111
|
+
* @param {object|null} params.state raw state.json (for active_tasks)
|
|
112
|
+
* @param {string} params.sharedRoot
|
|
113
|
+
* @returns {object|null}
|
|
114
|
+
*/
|
|
115
|
+
function buildWelcomeBack({ config, now, marker, state, sharedRoot }) {
|
|
116
|
+
const wb = config?.collaboration?.welcome_back
|
|
117
|
+
if (wb?.enabled === false) return null
|
|
118
|
+
|
|
119
|
+
const lastRunAt = marker?.last_run_at ? Date.parse(marker.last_run_at) : NaN
|
|
120
|
+
const thresholdHours = Number.isFinite(wb?.threshold_hours) ? wb.threshold_hours : 16
|
|
121
|
+
// Only fire when we have a baseline AND the gap strictly exceeds the threshold.
|
|
122
|
+
if (!Number.isFinite(lastRunAt)) return null
|
|
123
|
+
if (now - lastRunAt <= thresholdHours * HOUR_MS) return null
|
|
124
|
+
|
|
125
|
+
const activeTasks =
|
|
126
|
+
state?.active_tasks && typeof state.active_tasks === 'object' ? state.active_tasks : {}
|
|
127
|
+
const ids = Object.keys(activeTasks)
|
|
128
|
+
if (ids.length === 0) return null
|
|
129
|
+
|
|
130
|
+
// Pick the single most-recently-active task.
|
|
131
|
+
let bestId = null
|
|
132
|
+
let bestMs = -1
|
|
133
|
+
for (const id of ids) {
|
|
134
|
+
const ms = taskActivityMs(activeTasks[id])
|
|
135
|
+
if (ms > bestMs) {
|
|
136
|
+
bestMs = ms
|
|
137
|
+
bestId = id
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (!bestId) return null
|
|
141
|
+
|
|
142
|
+
const task = activeTasks[bestId]
|
|
143
|
+
const currentSubtask = task?.progress?.current_subtask ?? task?.current_subtask ?? null
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
task_id: bestId,
|
|
147
|
+
description: task?.description ?? bestId,
|
|
148
|
+
lifecycle_phase: task?.lifecycle_phase ?? null,
|
|
149
|
+
current_subtask: currentSubtask,
|
|
150
|
+
progress: task?.progress ?? null,
|
|
151
|
+
relative_time: bestMs > 0 ? relativeTime(now - bestMs) : null,
|
|
152
|
+
next_step: readNextStepLine(sharedRoot, bestId),
|
|
153
|
+
others_count: Math.max(0, ids.length - 1),
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Build the Team-Change Digest half (LD-04/06/07). Returns null when the
|
|
159
|
+
* visibility gate is closed (solo), the digest is disabled, or the
|
|
160
|
+
* default-branch sha has not moved since the marker.
|
|
161
|
+
*
|
|
162
|
+
* @param {object} params
|
|
163
|
+
* @param {string} params.projectRoot
|
|
164
|
+
* @param {object} params.config
|
|
165
|
+
* @param {object|null} params.marker
|
|
166
|
+
* @param {{ branch: string, sha: string }|null} params.defaultBranch
|
|
167
|
+
* @returns {object|null}
|
|
168
|
+
*/
|
|
169
|
+
function buildTeamDigest({ projectRoot, config, marker, defaultBranch }) {
|
|
170
|
+
const td = config?.collaboration?.team_digest
|
|
171
|
+
if (td?.enabled === false) return null
|
|
172
|
+
|
|
173
|
+
const visibility = resolveSharePolicy(config).visibility
|
|
174
|
+
if (visibility !== 'team' && visibility !== 'oss') return null
|
|
175
|
+
|
|
176
|
+
if (!defaultBranch?.sha) return null
|
|
177
|
+
// Only fire when main has actually moved since the recorded baseline.
|
|
178
|
+
if (marker?.last_main_sha && marker.last_main_sha === defaultBranch.sha) return null
|
|
179
|
+
|
|
180
|
+
const maxEntries = Number.isInteger(td?.max_entries) && td.max_entries > 0 ? td.max_entries : 7
|
|
181
|
+
const identity = (() => {
|
|
182
|
+
try {
|
|
183
|
+
return getGitIdentity(projectRoot)
|
|
184
|
+
} catch {
|
|
185
|
+
return null
|
|
186
|
+
}
|
|
187
|
+
})()
|
|
188
|
+
|
|
189
|
+
const { entries, more_count } = listMergedPullRequests(projectRoot, {
|
|
190
|
+
sinceSha: marker?.last_main_sha || null,
|
|
191
|
+
defaultBranch: defaultBranch.branch,
|
|
192
|
+
currentUserEmail: identity?.email || '',
|
|
193
|
+
maxEntries,
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
if (entries.length === 0 && more_count === 0) return null
|
|
197
|
+
return { entries, more_count }
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Compose the combined catch-up panel. Returns null when neither half fires.
|
|
202
|
+
*
|
|
203
|
+
* @param {string} projectRoot
|
|
204
|
+
* @param {object} params
|
|
205
|
+
* @param {object} params.config
|
|
206
|
+
* @param {number} params.now epoch ms
|
|
207
|
+
* @param {object|null} params.marker Last-Seen Marker
|
|
208
|
+
* @param {{ branch: string, sha: string }|null} params.defaultBranch
|
|
209
|
+
* @returns {{ welcome_back: object|null, team_digest: object|null }|null}
|
|
210
|
+
*
|
|
211
|
+
* @internal
|
|
212
|
+
*/
|
|
213
|
+
export function computeCatchUpPanel(projectRoot, { config, now, marker, defaultBranch } = {}) {
|
|
214
|
+
const sharedRoot = resolveSharedRoot(projectRoot)
|
|
215
|
+
const state = (() => {
|
|
216
|
+
const statePath = join(sharedRoot, '.aperant', 'state.json')
|
|
217
|
+
if (!existsSync(statePath)) return null
|
|
218
|
+
try {
|
|
219
|
+
return JSON.parse(readFileSync(statePath, 'utf-8'))
|
|
220
|
+
} catch {
|
|
221
|
+
return null
|
|
222
|
+
}
|
|
223
|
+
})()
|
|
224
|
+
|
|
225
|
+
const welcome_back = buildWelcomeBack({ config, now, marker, state, sharedRoot })
|
|
226
|
+
const team_digest = buildTeamDigest({ projectRoot, config, marker, defaultBranch })
|
|
227
|
+
|
|
228
|
+
if (!welcome_back && !team_digest) return null
|
|
229
|
+
// Fixed field order: welcome_back first (LD-10).
|
|
230
|
+
return { welcome_back, team_digest }
|
|
231
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @internal
|
|
3
|
+
* last-seen.mjs — per-machine, per-git-identity Last-Seen Marker (LD-01).
|
|
4
|
+
*
|
|
5
|
+
* The collaboration catch-up surfaces (Welcome-Back Summary + Team-Change
|
|
6
|
+
* Digest) both diff against one baseline record per human/machine:
|
|
7
|
+
*
|
|
8
|
+
* .aperant/last-seen/{email-slug}.json → { last_run_at, last_main_sha, last_active_task }
|
|
9
|
+
*
|
|
10
|
+
* The slug is derived from the git `user.email` (stable per human/machine),
|
|
11
|
+
* lowercased with every non-[a-z0-9] run collapsed to '-'. The directory is
|
|
12
|
+
* gitignored in the same per-machine ephemeral class as `team-status/` and
|
|
13
|
+
* `events/` (see upgrade-gitignore.mjs / share-policy.mjs).
|
|
14
|
+
*
|
|
15
|
+
* Modeled on team-status.mjs: resolveSharedRoot-based path so the marker lives
|
|
16
|
+
* in the main repo even when called from a worktree; atomicWriteJson +
|
|
17
|
+
* mkdirSync(recursive) for durable writes.
|
|
18
|
+
*
|
|
19
|
+
* This module is `@internal` — consumed only through the CoordinationStore
|
|
20
|
+
* seam (store.readLastSeen / store.advanceLastSeen). Do NOT import it from
|
|
21
|
+
* bin/apt-tools.mjs.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { existsSync, mkdirSync, readFileSync } from 'node:fs'
|
|
25
|
+
import { join } from 'node:path'
|
|
26
|
+
import { getGitIdentity } from '../git/identity.mjs'
|
|
27
|
+
import { atomicWriteJson } from '../util/io.mjs'
|
|
28
|
+
import { resolveSharedRoot } from './agent-identity.mjs'
|
|
29
|
+
|
|
30
|
+
function lastSeenDir(projectRoot) {
|
|
31
|
+
return join(resolveSharedRoot(projectRoot), '.aperant', 'last-seen')
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Slug a git email into a filesystem-safe key: lowercase, every run of
|
|
36
|
+
* non-[a-z0-9] characters collapsed to a single '-'.
|
|
37
|
+
*
|
|
38
|
+
* @param {string} email
|
|
39
|
+
* @returns {string}
|
|
40
|
+
*/
|
|
41
|
+
export function slugFromEmail(email) {
|
|
42
|
+
return String(email)
|
|
43
|
+
.toLowerCase()
|
|
44
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
45
|
+
.replace(/^-+|-+$/g, '')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function markerPath(projectRoot) {
|
|
49
|
+
const identity = (() => {
|
|
50
|
+
try {
|
|
51
|
+
return getGitIdentity(projectRoot)
|
|
52
|
+
} catch {
|
|
53
|
+
return null
|
|
54
|
+
}
|
|
55
|
+
})()
|
|
56
|
+
if (!identity?.email) return null
|
|
57
|
+
const slug = slugFromEmail(identity.email)
|
|
58
|
+
if (!slug) return null
|
|
59
|
+
return join(lastSeenDir(projectRoot), `${slug}.json`)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Read the current user's Last-Seen Marker. Returns null when the git email is
|
|
64
|
+
* absent (no identity → no key) or the file does not yet exist / is malformed.
|
|
65
|
+
*
|
|
66
|
+
* @param {string} projectRoot
|
|
67
|
+
* @returns {{ last_run_at: string|null, last_main_sha: string|null, last_active_task: string|null }|null}
|
|
68
|
+
*
|
|
69
|
+
* @internal
|
|
70
|
+
*/
|
|
71
|
+
export function readLastSeen(projectRoot) {
|
|
72
|
+
const path = markerPath(projectRoot)
|
|
73
|
+
if (!path || !existsSync(path)) return null
|
|
74
|
+
try {
|
|
75
|
+
const record = JSON.parse(readFileSync(path, 'utf-8'))
|
|
76
|
+
if (!record || typeof record !== 'object') return null
|
|
77
|
+
return {
|
|
78
|
+
last_run_at: record.last_run_at ?? null,
|
|
79
|
+
last_main_sha: record.last_main_sha ?? null,
|
|
80
|
+
last_active_task: record.last_active_task ?? null,
|
|
81
|
+
}
|
|
82
|
+
} catch {
|
|
83
|
+
return null
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Advance the marker, merging partial fields onto any existing record.
|
|
89
|
+
*
|
|
90
|
+
* Marker-advance semantics (LD-02):
|
|
91
|
+
* - last_run_at is always settable (it measures engagement).
|
|
92
|
+
* - last_main_sha is optional — the caller passes it only AFTER the digest
|
|
93
|
+
* was computed (even when empty). When undefined, the existing sha is kept.
|
|
94
|
+
* - last_active_task is optional — the welcome-back task id, kept otherwise.
|
|
95
|
+
*
|
|
96
|
+
* No-op (returns null) when the git email is absent — there is no key to write.
|
|
97
|
+
*
|
|
98
|
+
* @param {string} projectRoot
|
|
99
|
+
* @param {{ last_run_at?: string, last_main_sha?: string|null, last_active_task?: string|null }} fields
|
|
100
|
+
* @returns {object|null} the written record, or null when there is no identity key.
|
|
101
|
+
*
|
|
102
|
+
* @internal
|
|
103
|
+
*/
|
|
104
|
+
export function advanceLastSeen(projectRoot, fields = {}) {
|
|
105
|
+
const path = markerPath(projectRoot)
|
|
106
|
+
if (!path) return null
|
|
107
|
+
|
|
108
|
+
let existing = {}
|
|
109
|
+
if (existsSync(path)) {
|
|
110
|
+
try {
|
|
111
|
+
existing = JSON.parse(readFileSync(path, 'utf-8')) || {}
|
|
112
|
+
} catch {
|
|
113
|
+
existing = {}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const record = {
|
|
118
|
+
last_run_at:
|
|
119
|
+
fields.last_run_at !== undefined ? fields.last_run_at : (existing.last_run_at ?? null),
|
|
120
|
+
last_main_sha:
|
|
121
|
+
fields.last_main_sha !== undefined ? fields.last_main_sha : (existing.last_main_sha ?? null),
|
|
122
|
+
last_active_task:
|
|
123
|
+
fields.last_active_task !== undefined
|
|
124
|
+
? fields.last_active_task
|
|
125
|
+
: (existing.last_active_task ?? null),
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
mkdirSync(lastSeenDir(projectRoot), { recursive: true })
|
|
129
|
+
atomicWriteJson(path, record)
|
|
130
|
+
return record
|
|
131
|
+
}
|
|
@@ -33,6 +33,7 @@ import { atomicWriteJson } from '../util/io.mjs'
|
|
|
33
33
|
import { getAgentIdentity, resolveSharedRoot } from './agent-identity.mjs'
|
|
34
34
|
import { appendLifecycleEvent } from './event-log.mjs'
|
|
35
35
|
import { validateConductorEvent } from './event-schema.mjs'
|
|
36
|
+
import { advanceLastSeen as lastSeenAdvance, readLastSeen as lastSeenRead } from './last-seen.mjs'
|
|
36
37
|
import {
|
|
37
38
|
listActive as teamStatusListActive,
|
|
38
39
|
removeSelf as teamStatusRemoveSelf,
|
|
@@ -152,6 +153,23 @@ export function createCoordinationStore(projectRoot) {
|
|
|
152
153
|
}
|
|
153
154
|
},
|
|
154
155
|
|
|
156
|
+
/**
|
|
157
|
+
* @internal — LD-01: read the per-machine Last-Seen Marker. Returns
|
|
158
|
+
* null when the git email is absent or the marker has not been written.
|
|
159
|
+
*/
|
|
160
|
+
readLastSeen() {
|
|
161
|
+
return lastSeenRead(projectRoot)
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @internal — LD-02: advance the Last-Seen Marker. `last_run_at` is
|
|
166
|
+
* always settable; `last_main_sha` / `last_active_task` are optional
|
|
167
|
+
* partial fields merged onto the existing record.
|
|
168
|
+
*/
|
|
169
|
+
advanceLastSeen(fields = {}) {
|
|
170
|
+
return lastSeenAdvance(projectRoot, fields)
|
|
171
|
+
},
|
|
172
|
+
|
|
155
173
|
/** @internal */
|
|
156
174
|
getIdentity() {
|
|
157
175
|
return getAgentIdentity(projectRoot)
|
|
@@ -33,20 +33,21 @@
|
|
|
33
33
|
* authoring contract.
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
|
-
const VALID_SURFACES = new Set(['electron', 'web', 'cli', 'api', 'electron+web'])
|
|
36
|
+
const VALID_SURFACES = new Set(['electron', 'web', 'cli', 'api', 'mobile', 'electron+web'])
|
|
37
37
|
|
|
38
38
|
const HEADING_RE = /^(#{1,6})\s+(.*)$/
|
|
39
39
|
const USER_OUTCOMES_HEADING_RE = /^user\s+outcomes\s*$/i
|
|
40
40
|
const EPIC_RE = /^\*\*Epic:\*\*\s+(.+)$/
|
|
41
41
|
const EMPTY_WITH_NOTE_RE = /^_No user-observable changes — pure refactor\._\s*$/
|
|
42
42
|
const OUTCOME_RE =
|
|
43
|
-
/^-\s+\*\*ON\*\*\s+\[(electron|web|cli|api|electron\+web)\]:\s+(.+?)(\s+\*\(requires\s+ON\s+(\d+)\)\*)?\s*$/
|
|
43
|
+
/^-\s+\*\*ON\*\*\s+\[(electron|web|cli|api|mobile|electron\+web)\]:\s+(.+?)(\s+\*\(requires\s+ON\s+(\d+)\)\*)?\s*$/
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
46
|
* @typedef {Object} UserOutcome
|
|
47
47
|
* @property {string} id — synthesized positional ID (O1, O2, ...)
|
|
48
|
-
* @property {('electron'|'web'|'cli'|'api'|'electron+web')} surface
|
|
48
|
+
* @property {('electron'|'web'|'cli'|'api'|'mobile'|'electron+web')} surface
|
|
49
49
|
* @property {string} text — outcome prose (sans the surface tag and requires suffix)
|
|
50
|
+
* @property {string[]} inferred — v0.9.0 — indented child bullets under the parent outcome (raw prose, no further parsing). Defaults to [] when no children are present.
|
|
50
51
|
* @property {number} [requires] — the digit captured from `*(requires ON N)*`, when present and valid
|
|
51
52
|
*/
|
|
52
53
|
|
|
@@ -165,24 +166,58 @@ export function parseUserOutcomes(specContent) {
|
|
|
165
166
|
return result
|
|
166
167
|
}
|
|
167
168
|
|
|
168
|
-
// Parse outcome rows in document order.
|
|
169
|
-
//
|
|
169
|
+
// Parse outcome rows in document order. v0.9.0 — walk the raw body lines
|
|
170
|
+
// (not the trimmed `outcomeLines`) so we preserve indentation of child
|
|
171
|
+
// bullets. A `**ON**` parent row is followed by zero or more indented
|
|
172
|
+
// child bullets (lines starting with at least 2 spaces + `- `); those
|
|
173
|
+
// children attach to the parent as `inferred: string[]` (raw prose, no
|
|
174
|
+
// further parsing). The children block ends at the next `**ON**` row,
|
|
175
|
+
// a non-indented line, or a section terminator (blank line followed by
|
|
176
|
+
// another non-bullet non-blank line is fine; the next `**ON**` always
|
|
177
|
+
// resets ownership).
|
|
170
178
|
/** @type {UserOutcome[]} */
|
|
171
179
|
const outcomes = []
|
|
172
180
|
const rawRequires = []
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
181
|
+
const CHILD_RE = /^(?: {2}|\t)+- (.+)$/
|
|
182
|
+
let currentOutcomeIndex = -1
|
|
183
|
+
for (let i = 0; i < bodyLines.length; i++) {
|
|
184
|
+
const rawLine = bodyLines[i]
|
|
185
|
+
const trimmed = rawLine.trim()
|
|
186
|
+
if (trimmed.length === 0) {
|
|
187
|
+
// Blank lines don't close the parent — children may resume after
|
|
188
|
+
// a blank line (loose-list markdown). The next non-blank line
|
|
189
|
+
// decides ownership.
|
|
190
|
+
continue
|
|
191
|
+
}
|
|
192
|
+
const onMatch = trimmed.match(OUTCOME_RE)
|
|
193
|
+
if (onMatch) {
|
|
194
|
+
const surface = /** @type {UserOutcome['surface']} */ (onMatch[1])
|
|
195
|
+
if (!VALID_SURFACES.has(surface)) {
|
|
196
|
+
currentOutcomeIndex = -1
|
|
197
|
+
continue
|
|
198
|
+
}
|
|
199
|
+
const text = onMatch[2].trim()
|
|
200
|
+
const id = 'O' + (outcomes.length + 1)
|
|
201
|
+
const outcome = /** @type {UserOutcome} */ ({ id, surface, text, inferred: [] })
|
|
202
|
+
if (onMatch[4]) {
|
|
203
|
+
const n = Number.parseInt(onMatch[4], 10)
|
|
204
|
+
rawRequires.push({ outcomeIndex: outcomes.length, requires: n })
|
|
205
|
+
}
|
|
206
|
+
outcomes.push(outcome)
|
|
207
|
+
currentOutcomeIndex = outcomes.length - 1
|
|
208
|
+
continue
|
|
209
|
+
}
|
|
210
|
+
// Non-`**ON**` non-blank line. If it's an indented child bullet AND
|
|
211
|
+
// we have a current outcome, attach it. Otherwise (e.g. Epic line,
|
|
212
|
+
// sentinel, stray prose), drop ownership.
|
|
213
|
+
const childMatch = rawLine.match(CHILD_RE)
|
|
214
|
+
if (childMatch && currentOutcomeIndex >= 0) {
|
|
215
|
+
outcomes[currentOutcomeIndex].inferred.push(childMatch[1].trim())
|
|
216
|
+
continue
|
|
184
217
|
}
|
|
185
|
-
|
|
218
|
+
// Non-indented, non-`**ON**` line — closes the current outcome's
|
|
219
|
+
// child block. (The next `**ON**` will start a new block.)
|
|
220
|
+
currentOutcomeIndex = -1
|
|
186
221
|
}
|
|
187
222
|
|
|
188
223
|
if (outcomes.length === 0) {
|
package/src/cli/dispatch.mjs
CHANGED
|
@@ -36,9 +36,11 @@
|
|
|
36
36
|
|
|
37
37
|
import { cmdActive } from './commands/active.mjs'
|
|
38
38
|
import { cmdAdr } from './commands/adr.mjs'
|
|
39
|
+
// cmdApplyRecipe — loaded lazily in the dispatch handler (Fast Path PRF-001)
|
|
39
40
|
import { cmdAudit } from './commands/audit.mjs'
|
|
40
41
|
import { cmdAuthorSkill } from './commands/author-skill.mjs'
|
|
41
42
|
import { cmdBootstrap } from './commands/bootstrap.mjs'
|
|
43
|
+
import { cmdCatchUp } from './commands/catch-up.mjs'
|
|
42
44
|
import { cmdCheck } from './commands/check.mjs'
|
|
43
45
|
import { cmdCheckVersion } from './commands/check-version.mjs'
|
|
44
46
|
import { cmdCiWatch, cmdCiWatchSweep } from './commands/ci-watch.mjs'
|
|
@@ -69,12 +71,14 @@ import { cmdInitState } from './commands/init-state.mjs'
|
|
|
69
71
|
import { cmdInstallFromSource } from './commands/install-from-source.mjs'
|
|
70
72
|
import { cmdLock } from './commands/lock.mjs'
|
|
71
73
|
import { cmdMigratePrReviews } from './commands/migrate-pr-reviews.mjs'
|
|
74
|
+
import { cmdMobilePrepare } from './commands/mobile-prepare.mjs'
|
|
72
75
|
import { cmdPersonas } from './commands/personas.mjs'
|
|
73
76
|
import { cmdPlanApproval } from './commands/plan-approval.mjs'
|
|
74
77
|
import { cmdPrReview } from './commands/pr-review.mjs'
|
|
75
78
|
import { cmdPrecompactHook } from './commands/precompact-hook.mjs'
|
|
76
79
|
import { cmdProductContext } from './commands/product-context.mjs'
|
|
77
80
|
import { cmdProfile } from './commands/profile.mjs'
|
|
81
|
+
// cmdRecipeDiff — loaded lazily in the dispatch handler (Fast Path PRF-001)
|
|
78
82
|
import { cmdReleaseNotes } from './commands/release-notes.mjs'
|
|
79
83
|
import { cmdRiskCheck } from './commands/risk-check.mjs'
|
|
80
84
|
import { cmdRoadmap } from './commands/roadmap.mjs'
|
|
@@ -97,6 +101,7 @@ import { cmdValidateEvidence } from './commands/validate-evidence.mjs'
|
|
|
97
101
|
import { cmdVariantGallery } from './commands/variant-gallery.mjs'
|
|
98
102
|
import { cmdVariantPick } from './commands/variant-pick.mjs'
|
|
99
103
|
import { cmdVariantPool } from './commands/variant-pool.mjs'
|
|
104
|
+
// cmdVerifySandbox — loaded lazily in the dispatch handler (Fast Path PRF-001)
|
|
100
105
|
import { cmdVitestDoctor } from './commands/vitest-doctor.mjs'
|
|
101
106
|
import { cmdWave } from './commands/wave.mjs'
|
|
102
107
|
import { cmdWfrun } from './commands/wfrun.mjs'
|
|
@@ -212,11 +217,24 @@ export const DISPATCH = {
|
|
|
212
217
|
|
|
213
218
|
// ---- (positional dir, slice(2)) shape ----
|
|
214
219
|
state: (args) => wrap(cmdState)(args[1]),
|
|
220
|
+
'catch-up': (args) => wrap(cmdCatchUp)(args[1]),
|
|
215
221
|
config: (args) => wrap(cmdConfig)(args[1], args.slice(2)),
|
|
216
222
|
'state-update': (args) => wrap(cmdStateUpdate)(args[1], args.slice(2)),
|
|
217
223
|
'state-artifacts': (args) => wrap(cmdStateArtifacts)(args[1], args.slice(2)),
|
|
218
224
|
'detect-project': (args) => wrap(cmdDetectProject)(args[1]),
|
|
219
225
|
'detect-runtime': (args) => wrap(cmdDetectRuntime)(args[1], args.slice(2)),
|
|
226
|
+
'recipe-diff': async (args) => {
|
|
227
|
+
const { cmdRecipeDiff } = await import('./commands/recipe-diff.mjs')
|
|
228
|
+
return wrap(cmdRecipeDiff)(args[1], args.slice(2))
|
|
229
|
+
},
|
|
230
|
+
'apply-recipe': async (args) => {
|
|
231
|
+
const { cmdApplyRecipe } = await import('./commands/apply-recipe.mjs')
|
|
232
|
+
return wrap(cmdApplyRecipe)(args[1], args.slice(2))
|
|
233
|
+
},
|
|
234
|
+
'verify-sandbox': async (args) => {
|
|
235
|
+
const { cmdVerifySandbox } = await import('./commands/verify-sandbox.mjs')
|
|
236
|
+
return wrap(cmdVerifySandbox)(args[1], args.slice(2))
|
|
237
|
+
},
|
|
220
238
|
driver: (args) => {
|
|
221
239
|
// `apt-tools driver doctor <project-dir>` — only subcommand for now.
|
|
222
240
|
if (args[1] === 'doctor') return wrap(cmdDriverDoctor)(args[2], args.slice(3))
|
|
@@ -228,6 +246,17 @@ export const DISPATCH = {
|
|
|
228
246
|
exitCode: 1,
|
|
229
247
|
}))()
|
|
230
248
|
},
|
|
249
|
+
mobile: (args) => {
|
|
250
|
+
// `apt-tools mobile prepare <project-dir> --platform <ios|android> [--mode <expo|native>] [--device <id>]`
|
|
251
|
+
if (args[1] === 'prepare') return wrap(cmdMobilePrepare)(args[2], args.slice(3))
|
|
252
|
+
return wrap(() => ({
|
|
253
|
+
envelope: {
|
|
254
|
+
status: 'error',
|
|
255
|
+
error: `Usage: apt-tools mobile prepare <project-dir> --platform <ios|android>. Unknown subcommand: ${args[1] ?? '(none)'}`,
|
|
256
|
+
},
|
|
257
|
+
exitCode: 1,
|
|
258
|
+
}))()
|
|
259
|
+
},
|
|
231
260
|
'features-audit': (args) => wrap(cmdFeaturesAudit)(args[1], args.slice(2)),
|
|
232
261
|
'init-state': (args) => wrap(cmdInitState)(args[1], args.slice(2)),
|
|
233
262
|
'risk-check': (args) => wrap(cmdRiskCheck)(args[1], args.slice(2)),
|