peaks-cli 1.3.2 → 1.3.4
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/README.md +6 -2
- package/dist/src/cli/commands/core-artifact-commands.js +6 -3
- package/dist/src/cli/commands/gate-commands.js +28 -19
- package/dist/src/cli/commands/hook-handle.d.ts +17 -0
- package/dist/src/cli/commands/hook-handle.js +111 -0
- package/dist/src/cli/commands/hooks-commands.js +72 -21
- package/dist/src/cli/commands/progress-commands.js +9 -2
- package/dist/src/cli/commands/progress-start-spawn.js +30 -4
- package/dist/src/cli/commands/project-commands.js +8 -4
- package/dist/src/cli/commands/statusline-commands.js +75 -17
- package/dist/src/cli/commands/sub-agent-commands.d.ts +5 -0
- package/dist/src/cli/commands/sub-agent-commands.js +488 -0
- package/dist/src/cli/commands/sub-agent-dispatch-guard.d.ts +55 -0
- package/dist/src/cli/commands/sub-agent-dispatch-guard.js +57 -0
- package/dist/src/cli/commands/workflow-commands.js +2 -1
- package/dist/src/cli/commands/workspace-commands.js +3 -0
- package/dist/src/cli/program.js +9 -0
- package/dist/src/hooks/pre-tool-use-sub-agent.d.ts +28 -0
- package/dist/src/hooks/pre-tool-use-sub-agent.js +105 -0
- package/dist/src/services/config/config-types.d.ts +1 -1
- package/dist/src/services/context/artifact-meta.d.ts +72 -0
- package/dist/src/services/context/artifact-meta.js +105 -0
- package/dist/src/services/context/context-guard.d.ts +49 -0
- package/dist/src/services/context/context-guard.js +91 -0
- package/dist/src/services/context/dispatch-context-guard.d.ts +27 -0
- package/dist/src/services/context/dispatch-context-guard.js +192 -0
- package/dist/src/services/context/headroom-client.d.ts +34 -0
- package/dist/src/services/context/headroom-client.js +117 -0
- package/dist/src/services/context/shared-channel.d.ts +92 -0
- package/dist/src/services/context/shared-channel.js +285 -0
- package/dist/src/services/context/threshold.d.ts +35 -0
- package/dist/src/services/context/threshold.js +76 -0
- package/dist/src/services/dashboard/project-dashboard-service.d.ts +23 -0
- package/dist/src/services/dashboard/project-dashboard-service.js +21 -0
- package/dist/src/services/dispatch/batch-counter.d.ts +27 -0
- package/dist/src/services/dispatch/batch-counter.js +85 -0
- package/dist/src/services/dispatch/dispatch-record-writer.d.ts +93 -0
- package/dist/src/services/dispatch/dispatch-record-writer.js +261 -0
- package/dist/src/services/dispatch/heartbeat-truncator.d.ts +26 -0
- package/dist/src/services/dispatch/heartbeat-truncator.js +13 -0
- package/dist/src/services/dispatch/leak-detector.d.ts +11 -0
- package/dist/src/services/dispatch/leak-detector.js +72 -0
- package/dist/src/services/dispatch/sub-agent-dispatcher.d.ts +127 -0
- package/dist/src/services/dispatch/sub-agent-dispatcher.js +98 -0
- package/dist/src/services/ide/adapters/claude-code-adapter.d.ts +18 -0
- package/dist/src/services/ide/adapters/claude-code-adapter.js +80 -0
- package/dist/src/services/ide/adapters/trae-adapter.d.ts +42 -0
- package/dist/src/services/ide/adapters/trae-adapter.js +98 -0
- package/dist/src/services/ide/hook-protocol.d.ts +47 -0
- package/dist/src/services/ide/hook-protocol.js +74 -0
- package/dist/src/services/ide/hook-translator.d.ts +72 -0
- package/dist/src/services/ide/hook-translator.js +128 -0
- package/dist/src/services/ide/ide-detector.d.ts +10 -0
- package/dist/src/services/ide/ide-detector.js +19 -0
- package/dist/src/services/ide/ide-registry.d.ts +14 -0
- package/dist/src/services/ide/ide-registry.js +45 -0
- package/dist/src/services/ide/ide-types.d.ts +180 -0
- package/dist/src/services/ide/ide-types.js +2 -0
- package/dist/src/services/ide/resource-profile.d.ts +52 -0
- package/dist/src/services/ide/resource-profile.js +33 -0
- package/dist/src/services/ide/shared/atomic-json.d.ts +15 -0
- package/dist/src/services/ide/shared/atomic-json.js +58 -0
- package/dist/src/services/ide/shared/safe-path.d.ts +11 -0
- package/dist/src/services/ide/shared/safe-path.js +29 -0
- package/dist/src/services/memory/project-context-service.js +2 -1
- package/dist/src/services/memory/project-memory-service.js +4 -3
- package/dist/src/services/perf/perf-baseline-service.js +2 -1
- package/dist/src/services/progress/progress-service.d.ts +1 -1
- package/dist/src/services/progress/progress-service.js +18 -14
- package/dist/src/services/security/safe-settings-path.d.ts +12 -0
- package/dist/src/services/security/safe-settings-path.js +104 -0
- package/dist/src/services/session/getSessionDir.d.ts +1 -0
- package/dist/src/services/session/getSessionDir.js +27 -0
- package/dist/src/services/session/index.d.ts +1 -0
- package/dist/src/services/session/index.js +1 -0
- package/dist/src/services/signal/cancel-handler.d.ts +14 -0
- package/dist/src/services/signal/cancel-handler.js +76 -0
- package/dist/src/services/skill/resume-detector.d.ts +54 -0
- package/dist/src/services/skill/resume-detector.js +334 -0
- package/dist/src/services/skill/skill-scheduler.d.ts +40 -0
- package/dist/src/services/skill/skill-scheduler.js +53 -0
- package/dist/src/services/skills/hooks-settings-service.d.ts +47 -29
- package/dist/src/services/skills/hooks-settings-service.js +190 -144
- package/dist/src/services/skills/statusline-settings-service.d.ts +33 -6
- package/dist/src/services/skills/statusline-settings-service.js +31 -34
- package/dist/src/services/slice/slice-archive-service.d.ts +20 -0
- package/dist/src/services/slice/slice-archive-service.js +111 -0
- package/dist/src/services/solo/batch-heartbeat-poller.d.ts +51 -0
- package/dist/src/services/solo/batch-heartbeat-poller.js +88 -0
- package/dist/src/services/solo/status-line-renderer.d.ts +34 -0
- package/dist/src/services/solo/status-line-renderer.js +55 -0
- package/dist/src/services/standards/ide-aware-standards-service.d.ts +94 -0
- package/dist/src/services/standards/ide-aware-standards-service.js +89 -0
- package/dist/src/services/standards/project-standards-service.d.ts +1 -2
- package/dist/src/services/workspace/reconcile-service.d.ts +36 -0
- package/dist/src/services/workspace/reconcile-service.js +107 -6
- package/dist/src/services/workspace/reconcile-types.d.ts +12 -0
- package/dist/src/shared/version.d.ts +1 -1
- package/dist/src/shared/version.js +1 -1
- package/package.json +2 -1
- package/scripts/install-skills.mjs +112 -2
- package/skills/peaks-ide/SKILL.md +159 -0
- package/skills/peaks-ide/references/audit-log-helper.md +52 -0
- package/skills/peaks-qa/SKILL.md +153 -55
- package/skills/peaks-qa/references/qa-fanout-contract.md +150 -0
- package/skills/peaks-rd/SKILL.md +134 -62
- package/skills/peaks-solo/SKILL.md +124 -37
- package/skills/peaks-solo/references/browser-workflow.md +22 -20
- package/skills/peaks-solo/references/context-governance.md +144 -0
- package/skills/peaks-solo/references/headroom-integration.md +107 -0
- package/skills/peaks-solo/references/runbook.md +3 -3
- package/skills/peaks-solo/references/sub-agent-dispatch.md +261 -0
- package/skills/peaks-solo/references/swarm-dispatch-contract.md +3 -37
- package/skills/peaks-txt/SKILL.md +17 -0
- package/skills/peaks-ui/SKILL.md +45 -10
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import { closeSync, constants, existsSync,
|
|
1
|
+
import { closeSync, constants, existsSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
|
-
import { dirname, isAbsolute, join,
|
|
3
|
+
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
|
+
import { assertSafeSettingsFile, isInsidePath } from '../ide/shared/safe-path.js';
|
|
6
|
+
import { getAdapter } from '../ide/ide-registry.js';
|
|
5
7
|
export const STATUSLINE_COMMAND = 'peaks statusline';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
// Re-export the shared helper so existing consumers that imported
|
|
9
|
+
// `isInsidePath` from this module keep compiling.
|
|
10
|
+
export { isInsidePath };
|
|
11
|
+
function resolveIde(options) {
|
|
12
|
+
return options?.ide ?? 'claude-code';
|
|
9
13
|
}
|
|
10
14
|
function resolveSettingsRoot(scope, projectRoot) {
|
|
11
15
|
if (scope === 'global')
|
|
@@ -15,25 +19,17 @@ function resolveSettingsRoot(scope, projectRoot) {
|
|
|
15
19
|
}
|
|
16
20
|
return resolve(projectRoot);
|
|
17
21
|
}
|
|
18
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Resolve + safety-check the settings path for the given IDE and scope. The
|
|
24
|
+
* `dirName` and `settingsFileName` come from the registered adapter
|
|
25
|
+
* (`getAdapter(ide)`) so the hardcoded `.claude/settings.json` is gone —
|
|
26
|
+
* future adapters swap by changing the registry, not this file.
|
|
27
|
+
*/
|
|
28
|
+
function resolveAndAssertSettingsPath(scope, ide, projectRoot) {
|
|
19
29
|
const root = resolveSettingsRoot(scope, projectRoot);
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function assertSafeSettingsPath(scope, root, settingsPath) {
|
|
24
|
-
const claudeDir = join(root, '.claude');
|
|
25
|
-
if (existsSync(claudeDir) && lstatSync(claudeDir).isSymbolicLink()) {
|
|
26
|
-
throw new Error('.claude directory must not be a symlink');
|
|
27
|
-
}
|
|
28
|
-
if (existsSync(settingsPath)) {
|
|
29
|
-
if (lstatSync(settingsPath).isSymbolicLink()) {
|
|
30
|
-
throw new Error('settings.json must not be a symlink');
|
|
31
|
-
}
|
|
32
|
-
const realRoot = realpathSync(root);
|
|
33
|
-
if (!isInsidePath(realpathSync(settingsPath), realRoot)) {
|
|
34
|
-
throw new Error(`settings.json must stay inside the ${scope} root`);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
30
|
+
const adapter = getAdapter(ide);
|
|
31
|
+
const { settingsPath } = assertSafeSettingsFile(scope, root, adapter.settings.dirName, adapter.settings.settingsFileName);
|
|
32
|
+
return { root, settingsPath };
|
|
37
33
|
}
|
|
38
34
|
function readSettings(settingsPath) {
|
|
39
35
|
if (!existsSync(settingsPath))
|
|
@@ -76,10 +72,9 @@ function buildPlan(scope, settingsPath, settings, exists) {
|
|
|
76
72
|
desiredCommand: STATUSLINE_COMMAND
|
|
77
73
|
};
|
|
78
74
|
}
|
|
79
|
-
export function planStatusLineInstall(scope, projectRoot) {
|
|
80
|
-
const
|
|
81
|
-
const settingsPath =
|
|
82
|
-
assertSafeSettingsPath(scope, root, settingsPath);
|
|
75
|
+
export function planStatusLineInstall(scope, projectRoot, options) {
|
|
76
|
+
const ide = resolveIde(options);
|
|
77
|
+
const { settingsPath } = resolveAndAssertSettingsPath(scope, ide, projectRoot);
|
|
83
78
|
const exists = existsSync(settingsPath);
|
|
84
79
|
const settings = readSettings(settingsPath);
|
|
85
80
|
return buildPlan(scope, settingsPath, settings, exists);
|
|
@@ -109,9 +104,8 @@ function atomicWriteJson(settingsPath, settings) {
|
|
|
109
104
|
}
|
|
110
105
|
}
|
|
111
106
|
export function applyStatusLineInstall(scope, projectRoot, options = {}) {
|
|
112
|
-
const
|
|
113
|
-
const settingsPath =
|
|
114
|
-
assertSafeSettingsPath(scope, root, settingsPath);
|
|
107
|
+
const ide = resolveIde(options);
|
|
108
|
+
const { settingsPath } = resolveAndAssertSettingsPath(scope, ide, projectRoot);
|
|
115
109
|
const exists = existsSync(settingsPath);
|
|
116
110
|
const settings = readSettings(settingsPath);
|
|
117
111
|
const plan = buildPlan(scope, settingsPath, settings, exists);
|
|
@@ -126,10 +120,9 @@ export function applyStatusLineInstall(scope, projectRoot, options = {}) {
|
|
|
126
120
|
atomicWriteJson(settingsPath, nextSettings);
|
|
127
121
|
return { ...plan, applied: true };
|
|
128
122
|
}
|
|
129
|
-
export function removeStatusLineInstall(scope, projectRoot) {
|
|
130
|
-
const
|
|
131
|
-
const settingsPath =
|
|
132
|
-
assertSafeSettingsPath(scope, root, settingsPath);
|
|
123
|
+
export function removeStatusLineInstall(scope, projectRoot, options) {
|
|
124
|
+
const ide = resolveIde(options);
|
|
125
|
+
const { settingsPath } = resolveAndAssertSettingsPath(scope, ide, projectRoot);
|
|
133
126
|
if (!existsSync(settingsPath)) {
|
|
134
127
|
return { scope, settingsPath, removed: false };
|
|
135
128
|
}
|
|
@@ -142,3 +135,7 @@ export function removeStatusLineInstall(scope, projectRoot) {
|
|
|
142
135
|
atomicWriteJson(settingsPath, rest);
|
|
143
136
|
return { scope, settingsPath, removed: true };
|
|
144
137
|
}
|
|
138
|
+
// Suppress unused-import warning for `isAbsolute` if it becomes unused in
|
|
139
|
+
// future refactors. The pre-refactor file used it in the local isInsidePath;
|
|
140
|
+
// the shared helper owns that logic now.
|
|
141
|
+
void isAbsolute;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare const ARCHIVE_RETENTION_MS: number;
|
|
2
|
+
export interface ArchiveResult {
|
|
3
|
+
readonly archivedCompleted: number;
|
|
4
|
+
readonly archivedInFlight: number;
|
|
5
|
+
readonly gcDeleted: number;
|
|
6
|
+
}
|
|
7
|
+
/** Build the canonical archive dir for a given session + slice id. */
|
|
8
|
+
export declare function archiveDir(projectRoot: string, sessionId: string, sliceId: string): string;
|
|
9
|
+
/** Build the in-flight subdir (records not yet disposed). */
|
|
10
|
+
export declare function inFlightArchiveDir(projectRoot: string, sessionId: string, sliceId: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* Archive the current `.peaks/_sub_agents/<sid>/` tree under
|
|
13
|
+
* `<archiveDir>` (per the sliceId), separating completed vs in-flight.
|
|
14
|
+
* Then run the 30-day GC over the archive dir.
|
|
15
|
+
*/
|
|
16
|
+
export declare function archiveSubAgentRecords(projectRoot: string, options: {
|
|
17
|
+
sessionId: string;
|
|
18
|
+
sliceId: string;
|
|
19
|
+
now?: () => Date;
|
|
20
|
+
}): ArchiveResult;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slice #009 / G5 RL-8 — sub-agent dispatch record archival + 30-day GC.
|
|
3
|
+
*
|
|
4
|
+
* Called by the `peaks session finish` / `peaks session abandon` /
|
|
5
|
+
* new-rid-startup hooks. Walks the per-session `.peaks/_sub_agents/<sid>/`
|
|
6
|
+
* tree and moves completed + disposed records to
|
|
7
|
+
* `.peaks/_runtime/<sid>/_archive/_sub_agents/<sliceId>/`.
|
|
8
|
+
*
|
|
9
|
+
* GC policy:
|
|
10
|
+
* - Records with `disposed: true` AND `outcome ∈ {success, failed,
|
|
11
|
+
* timeout, cancelled}` (not "no-execution") → archive + 30-day GC.
|
|
12
|
+
* - Records with `disposed: false` (any outcome) → archive but NOT
|
|
13
|
+
* GC'd. Next session will see them as still-pending in
|
|
14
|
+
* `_archive/.../in-flight/` and can resume reducer work.
|
|
15
|
+
*
|
|
16
|
+
* Lazy GC: `archiveSubAgentRecords` also scans the archive dir and
|
|
17
|
+
* deletes entries older than 30 days. No cron, no background daemon.
|
|
18
|
+
*/
|
|
19
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, unlinkSync } from 'node:fs';
|
|
20
|
+
import { join } from 'node:path';
|
|
21
|
+
import { isDispatchStatus, isOutcome } from '../dispatch/dispatch-record-writer.js';
|
|
22
|
+
export const ARCHIVE_RETENTION_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
|
|
23
|
+
/** Build the canonical archive dir for a given session + slice id. */
|
|
24
|
+
export function archiveDir(projectRoot, sessionId, sliceId) {
|
|
25
|
+
return join(projectRoot, '.peaks', '_runtime', sessionId, '_archive', '_sub_agents', sliceId);
|
|
26
|
+
}
|
|
27
|
+
/** Build the in-flight subdir (records not yet disposed). */
|
|
28
|
+
export function inFlightArchiveDir(projectRoot, sessionId, sliceId) {
|
|
29
|
+
return join(archiveDir(projectRoot, sessionId, sliceId), 'in-flight');
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Archive the current `.peaks/_sub_agents/<sid>/` tree under
|
|
33
|
+
* `<archiveDir>` (per the sliceId), separating completed vs in-flight.
|
|
34
|
+
* Then run the 30-day GC over the archive dir.
|
|
35
|
+
*/
|
|
36
|
+
export function archiveSubAgentRecords(projectRoot, options) {
|
|
37
|
+
const now = options.now ?? (() => new Date());
|
|
38
|
+
const sourceDir = join(projectRoot, '.peaks', '_sub_agents', options.sessionId);
|
|
39
|
+
const completedDir = archiveDir(projectRoot, options.sessionId, options.sliceId);
|
|
40
|
+
const inFlightDir = inFlightArchiveDir(projectRoot, options.sessionId, options.sliceId);
|
|
41
|
+
mkdirSync(completedDir, { recursive: true });
|
|
42
|
+
mkdirSync(inFlightDir, { recursive: true });
|
|
43
|
+
let archivedCompleted = 0;
|
|
44
|
+
let archivedInFlight = 0;
|
|
45
|
+
if (existsSync(sourceDir)) {
|
|
46
|
+
for (const entry of readdirSync(sourceDir)) {
|
|
47
|
+
if (!entry.startsWith('dispatch-') || !entry.endsWith('.json'))
|
|
48
|
+
continue;
|
|
49
|
+
const src = join(sourceDir, entry);
|
|
50
|
+
const record = readRecordOrNull(src);
|
|
51
|
+
if (record === null)
|
|
52
|
+
continue;
|
|
53
|
+
if (record.disposed === true && isCompletedOutcome(record.outcome)) {
|
|
54
|
+
renameSync(src, join(completedDir, entry));
|
|
55
|
+
archivedCompleted += 1;
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
renameSync(src, join(inFlightDir, entry));
|
|
59
|
+
archivedInFlight += 1;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// 30-day GC: walk the archive root (any sliceId), delete entries
|
|
64
|
+
// whose file mtime is older than the retention.
|
|
65
|
+
const gcDeleted = runGarbageCollection(completedDir, now().getTime());
|
|
66
|
+
return { archivedCompleted, archivedInFlight, gcDeleted };
|
|
67
|
+
}
|
|
68
|
+
function isCompletedOutcome(outcome) {
|
|
69
|
+
return outcome === 'success' || outcome === 'failed' || outcome === 'timeout' || outcome === 'cancelled';
|
|
70
|
+
}
|
|
71
|
+
function readRecordOrNull(path) {
|
|
72
|
+
try {
|
|
73
|
+
const raw = readFileSync(path, 'utf8');
|
|
74
|
+
const parsed = JSON.parse(raw);
|
|
75
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
76
|
+
return null;
|
|
77
|
+
const obj = parsed;
|
|
78
|
+
if (!isOutcome(obj.outcome) || !isDispatchStatus(obj.status))
|
|
79
|
+
return null;
|
|
80
|
+
if (typeof obj.disposed !== 'boolean')
|
|
81
|
+
return null;
|
|
82
|
+
return obj;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function runGarbageCollection(dir, nowMs) {
|
|
89
|
+
if (!existsSync(dir))
|
|
90
|
+
return 0;
|
|
91
|
+
let deleted = 0;
|
|
92
|
+
for (const entry of readdirSync(dir)) {
|
|
93
|
+
const full = join(dir, entry);
|
|
94
|
+
try {
|
|
95
|
+
const stat = statSync(full);
|
|
96
|
+
if (stat.isDirectory()) {
|
|
97
|
+
deleted += runGarbageCollection(full, nowMs);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const ageMs = nowMs - stat.mtimeMs;
|
|
101
|
+
if (ageMs > ARCHIVE_RETENTION_MS) {
|
|
102
|
+
unlinkSync(full);
|
|
103
|
+
deleted += 1;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
/* skip unreadable; do not crash the archive op */
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return deleted;
|
|
111
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { summarize, type SubAgentLiveView } from './status-line-renderer.js';
|
|
2
|
+
export declare const DEFAULT_POLL_INTERVAL_MS = 10000;
|
|
3
|
+
export declare const DEFAULT_STALE_THRESHOLD_MS: number;
|
|
4
|
+
export type PollRecord = {
|
|
5
|
+
readonly path: string;
|
|
6
|
+
readonly role: string;
|
|
7
|
+
};
|
|
8
|
+
export type PollerStatusEvent = {
|
|
9
|
+
readonly kind: 'status';
|
|
10
|
+
readonly line: string;
|
|
11
|
+
readonly summary: ReturnType<typeof summarize>;
|
|
12
|
+
readonly views: readonly SubAgentLiveView[];
|
|
13
|
+
};
|
|
14
|
+
export type PollerStaleEvent = {
|
|
15
|
+
readonly kind: 'stale';
|
|
16
|
+
readonly path: string;
|
|
17
|
+
readonly role: string;
|
|
18
|
+
readonly lastBeatAgoSec: number;
|
|
19
|
+
readonly thresholdSec: number;
|
|
20
|
+
};
|
|
21
|
+
export type PollerDoneEvent = {
|
|
22
|
+
readonly kind: 'done';
|
|
23
|
+
readonly summary: ReturnType<typeof summarize>;
|
|
24
|
+
};
|
|
25
|
+
export type PollerEvent = PollerStatusEvent | PollerStaleEvent | PollerDoneEvent;
|
|
26
|
+
export type PollerHandlers = {
|
|
27
|
+
onStatus?: (event: PollerStatusEvent) => void;
|
|
28
|
+
onStale?: (event: PollerStaleEvent) => void;
|
|
29
|
+
onDone?: (event: PollerDoneEvent) => void;
|
|
30
|
+
onError?: (error: unknown) => void;
|
|
31
|
+
};
|
|
32
|
+
export type PollerOptions = {
|
|
33
|
+
prefix: string;
|
|
34
|
+
intervalMs?: number;
|
|
35
|
+
staleThresholdMs?: number;
|
|
36
|
+
now?: () => Date;
|
|
37
|
+
};
|
|
38
|
+
export declare class BatchHeartbeatPoller {
|
|
39
|
+
private readonly records;
|
|
40
|
+
private readonly handlers;
|
|
41
|
+
private readonly options;
|
|
42
|
+
private timer;
|
|
43
|
+
private prevStale;
|
|
44
|
+
private prevSummaryDone;
|
|
45
|
+
private running;
|
|
46
|
+
constructor(records: readonly PollRecord[], handlers: PollerHandlers, options: PollerOptions);
|
|
47
|
+
start(): void;
|
|
48
|
+
stop(): void;
|
|
49
|
+
/** Force a tick (testing seam + low-level access for explicit invocation). */
|
|
50
|
+
tick(): void;
|
|
51
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { readRecords } from '../dispatch/dispatch-record-writer.js';
|
|
2
|
+
import { renderStatusLine, summarize, viewSubAgent } from './status-line-renderer.js';
|
|
3
|
+
export const DEFAULT_POLL_INTERVAL_MS = 10_000;
|
|
4
|
+
export const DEFAULT_STALE_THRESHOLD_MS = 5 * 60 * 1000;
|
|
5
|
+
const TERMINAL_STATUSES = ['done', 'failed'];
|
|
6
|
+
const TERMINAL_RECORD_STATUSES = [
|
|
7
|
+
'done',
|
|
8
|
+
'failed',
|
|
9
|
+
'cancelled',
|
|
10
|
+
'no-execution'
|
|
11
|
+
];
|
|
12
|
+
export class BatchHeartbeatPoller {
|
|
13
|
+
records;
|
|
14
|
+
handlers;
|
|
15
|
+
options;
|
|
16
|
+
timer = null;
|
|
17
|
+
prevStale = new Set();
|
|
18
|
+
prevSummaryDone = 0;
|
|
19
|
+
running = false;
|
|
20
|
+
constructor(records, handlers, options) {
|
|
21
|
+
this.records = records;
|
|
22
|
+
this.handlers = handlers;
|
|
23
|
+
this.options = options;
|
|
24
|
+
}
|
|
25
|
+
start() {
|
|
26
|
+
if (this.running)
|
|
27
|
+
return;
|
|
28
|
+
this.running = true;
|
|
29
|
+
this.tick();
|
|
30
|
+
const interval = this.options.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
31
|
+
this.timer = setInterval(() => this.tick(), interval);
|
|
32
|
+
// Don't keep the process alive just for the poller.
|
|
33
|
+
this.timer.unref?.();
|
|
34
|
+
}
|
|
35
|
+
stop() {
|
|
36
|
+
this.running = false;
|
|
37
|
+
if (this.timer) {
|
|
38
|
+
clearInterval(this.timer);
|
|
39
|
+
this.timer = null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/** Force a tick (testing seam + low-level access for explicit invocation). */
|
|
43
|
+
tick() {
|
|
44
|
+
let recs;
|
|
45
|
+
try {
|
|
46
|
+
recs = readRecords(this.records.map((r) => r.path));
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
this.handlers.onError?.(error);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const now = this.options.now ?? (() => new Date());
|
|
53
|
+
const summary = summarize(recs);
|
|
54
|
+
const views = recs.map((r) => viewSubAgent(r, now));
|
|
55
|
+
const line = renderStatusLine(this.options.prefix, recs, now);
|
|
56
|
+
this.handlers.onStatus?.({ kind: 'status', line, summary, views });
|
|
57
|
+
const staleThresholdSec = Math.floor((this.options.staleThresholdMs ?? DEFAULT_STALE_THRESHOLD_MS) / 1000);
|
|
58
|
+
for (const rec of recs) {
|
|
59
|
+
const v = viewSubAgent(rec, now);
|
|
60
|
+
if (v.isStale) {
|
|
61
|
+
const key = `${rec.role}::${rec.requestId}`;
|
|
62
|
+
if (!this.prevStale.has(key)) {
|
|
63
|
+
this.prevStale.add(key);
|
|
64
|
+
this.handlers.onStale?.({
|
|
65
|
+
kind: 'stale',
|
|
66
|
+
path: this.records.find((p) => p.role === rec.role)?.path ?? '',
|
|
67
|
+
role: rec.role,
|
|
68
|
+
lastBeatAgoSec: v.lastBeatAgoSec ?? -1,
|
|
69
|
+
thresholdSec: staleThresholdSec
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (summary.total > 0 && summary.done === summary.total && this.prevSummaryDone !== summary.done) {
|
|
75
|
+
this.prevSummaryDone = summary.done;
|
|
76
|
+
this.handlers.onDone?.({ kind: 'done', summary });
|
|
77
|
+
this.stop();
|
|
78
|
+
}
|
|
79
|
+
else if (recs.length > 0 &&
|
|
80
|
+
recs.every((r) => TERMINAL_RECORD_STATUSES.includes(r.status) || TERMINAL_STATUSES.includes(r.status))) {
|
|
81
|
+
this.handlers.onDone?.({ kind: 'done', summary });
|
|
82
|
+
this.stop();
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
this.prevSummaryDone = summary.done;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* G6.5 — status line renderer for the peaks-solo batch-sync wait period.
|
|
3
|
+
*
|
|
4
|
+
* Single line, 80-120 chars, status-line-friendly. The shape is
|
|
5
|
+
* documented in PRD §G6.5:
|
|
6
|
+
*
|
|
7
|
+
* [peaks-solo] swarm 3/3 running | rd-planning 45% (12s ago) | qa-test-cases 30% (5s ago) | ui-design 20% (2s ago)
|
|
8
|
+
* [peaks-solo] swarm 3/3 running | rd-planning 70% (8s ago) | qa-test-cases 50% (3s ago) | ui-design 30% (6s ago)
|
|
9
|
+
* ...
|
|
10
|
+
* [peaks-solo] swarm 3/3 done in 47.3s
|
|
11
|
+
*
|
|
12
|
+
* Pure helper; the poller calls it once per tick. No IO.
|
|
13
|
+
*/
|
|
14
|
+
import type { DispatchRecord } from '../dispatch/dispatch-record-writer.js';
|
|
15
|
+
export type SubAgentLiveView = {
|
|
16
|
+
readonly role: string;
|
|
17
|
+
readonly status: string;
|
|
18
|
+
readonly progress: number | null;
|
|
19
|
+
readonly lastBeatAgoSec: number | null;
|
|
20
|
+
readonly isStale: boolean;
|
|
21
|
+
};
|
|
22
|
+
export type SwarmSummary = {
|
|
23
|
+
readonly total: number;
|
|
24
|
+
readonly running: number;
|
|
25
|
+
readonly done: number;
|
|
26
|
+
readonly failed: number;
|
|
27
|
+
readonly stale: number;
|
|
28
|
+
};
|
|
29
|
+
/** Build a per-sub-agent view of the current state of one record. */
|
|
30
|
+
export declare function viewSubAgent(record: DispatchRecord, now?: () => Date): SubAgentLiveView;
|
|
31
|
+
/** Aggregate swarm summary. */
|
|
32
|
+
export declare function summarize(records: readonly DispatchRecord[]): SwarmSummary;
|
|
33
|
+
/** Render a single status line. */
|
|
34
|
+
export declare function renderStatusLine(prefix: string, records: readonly DispatchRecord[], now?: () => Date): string;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const STALE_THRESHOLD_SEC = 5 * 60;
|
|
2
|
+
/** Build a per-sub-agent view of the current state of one record. */
|
|
3
|
+
export function viewSubAgent(record, now = () => new Date()) {
|
|
4
|
+
const latest = record.heartbeats[record.heartbeats.length - 1];
|
|
5
|
+
const lastBeatAgo = record.lastBeatAt
|
|
6
|
+
? Math.max(0, Math.floor((now().getTime() - new Date(record.lastBeatAt).getTime()) / 1000))
|
|
7
|
+
: null;
|
|
8
|
+
const isStale = lastBeatAgo !== null && lastBeatAgo > STALE_THRESHOLD_SEC;
|
|
9
|
+
return {
|
|
10
|
+
role: record.role,
|
|
11
|
+
status: record.status,
|
|
12
|
+
progress: latest ? latest.progress : null,
|
|
13
|
+
lastBeatAgoSec: lastBeatAgo,
|
|
14
|
+
isStale
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** Aggregate swarm summary. */
|
|
18
|
+
export function summarize(records) {
|
|
19
|
+
let running = 0;
|
|
20
|
+
let done = 0;
|
|
21
|
+
let failed = 0;
|
|
22
|
+
let stale = 0;
|
|
23
|
+
for (const r of records) {
|
|
24
|
+
const v = viewSubAgent(r);
|
|
25
|
+
if (v.isStale)
|
|
26
|
+
stale += 1;
|
|
27
|
+
if (r.status === 'done')
|
|
28
|
+
done += 1;
|
|
29
|
+
else if (r.status === 'failed' || r.status === 'cancelled')
|
|
30
|
+
failed += 1;
|
|
31
|
+
else
|
|
32
|
+
running += 1;
|
|
33
|
+
}
|
|
34
|
+
return { total: records.length, running, done, failed, stale };
|
|
35
|
+
}
|
|
36
|
+
/** Render a single status line. */
|
|
37
|
+
export function renderStatusLine(prefix, records, now = () => new Date()) {
|
|
38
|
+
if (records.length === 0) {
|
|
39
|
+
return `${prefix} swarm 0/0 idle`;
|
|
40
|
+
}
|
|
41
|
+
const summary = summarize(records);
|
|
42
|
+
const allDone = summary.done === summary.total;
|
|
43
|
+
if (allDone) {
|
|
44
|
+
return `${prefix} swarm ${summary.done}/${summary.total} done`;
|
|
45
|
+
}
|
|
46
|
+
const parts = records.map((r) => renderOne(r, now));
|
|
47
|
+
return `${prefix} swarm ${summary.running}/${summary.total} running | ${parts.join(' | ')}`;
|
|
48
|
+
}
|
|
49
|
+
function renderOne(record, now) {
|
|
50
|
+
const view = viewSubAgent(record, now);
|
|
51
|
+
const pct = view.progress !== null ? `${view.progress}%` : '?%';
|
|
52
|
+
const ago = view.lastBeatAgoSec !== null ? `${view.lastBeatAgoSec}s ago` : 'no beat';
|
|
53
|
+
const stale = view.isStale ? ' ⚠ stale' : '';
|
|
54
|
+
return `${view.role} ${pct} (${ago})${stale}`;
|
|
55
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IDE-aware wrapper for `peaks standards init` / `peaks standards update`.
|
|
3
|
+
*
|
|
4
|
+
* Slice #011-2026-06-07-ide-adapter-resource-profile: the original
|
|
5
|
+
* `executeProjectStandardsInit` / `executeProjectStandardsUpdate` always
|
|
6
|
+
* wrote to `CLAUDE.md` + `.claude/rules/**` regardless of which IDE
|
|
7
|
+
* the user was running. This wrapper dispatches on the IDE detected
|
|
8
|
+
* (or explicitly requested via `--ide`) and falls back to the legacy
|
|
9
|
+
* Claude Code path with a stderr warning when the detected IDE has
|
|
10
|
+
* no `standardsProfile` declared (Trae in slice 1.3.2).
|
|
11
|
+
*
|
|
12
|
+
* Two entry points:
|
|
13
|
+
*
|
|
14
|
+
* - `executeProjectStandardsInitIdeAware` — same signature as the
|
|
15
|
+
* underlying `executeProjectStandardsInit`, plus an optional
|
|
16
|
+
* `ideId` override that bypasses detection.
|
|
17
|
+
* - `executeProjectStandardsUpdateIdeAware` — same shape, for the
|
|
18
|
+
* `update` flow.
|
|
19
|
+
*
|
|
20
|
+
* Detection precedence:
|
|
21
|
+
* 1. Explicit `options.ideId` (CLI `--ide` flag)
|
|
22
|
+
* 2. `IdeRegistry.detect()` from cwd (or the `projectRoot` if given)
|
|
23
|
+
* 3. `null` (no IDE detected) → fall back to the legacy Claude Code path
|
|
24
|
+
*
|
|
25
|
+
* Fallback behavior: when the resolved IDE has no `standardsProfile`
|
|
26
|
+
* declared, the wrapper STILL calls the legacy Claude Code writer
|
|
27
|
+
* (so the user gets the files they would have gotten before slice #011)
|
|
28
|
+
* and emits a stderr warning with the IDE id and the fact that the
|
|
29
|
+
* adapter is UNVERIFIED for the standards profile. This keeps the
|
|
30
|
+
* "Trae is UNVERIFIED, ship a working file tree, surface the gap"
|
|
31
|
+
* contract intact.
|
|
32
|
+
*/
|
|
33
|
+
import type { IdeId } from '../ide/ide-types.js';
|
|
34
|
+
import { detectAllResourceTargets, getStandardsProfile } from '../ide/resource-profile.js';
|
|
35
|
+
import { type ProjectStandardsInitOptions, type ProjectStandardsInitResult, type ProjectStandardsUpdateResult } from './project-standards-service.js';
|
|
36
|
+
export type { ProjectStandardsInitResult, ProjectStandardsUpdateResult };
|
|
37
|
+
export type ProjectStandardsIdeAwareOptions = ProjectStandardsInitOptions & {
|
|
38
|
+
/**
|
|
39
|
+
* Explicit IDE override. When set, bypasses `IdeRegistry.detect()`
|
|
40
|
+
* (cwd + env heuristics) and uses the provided IDE id directly.
|
|
41
|
+
* Mirrors the `peaks hooks install --ide <id>` pattern.
|
|
42
|
+
*/
|
|
43
|
+
readonly ideId?: IdeId;
|
|
44
|
+
};
|
|
45
|
+
export type ProjectStandardsUpdateIdeAwareOptions = ProjectStandardsInitOptions & {
|
|
46
|
+
/** Explicit IDE override. See {@link ProjectStandardsIdeAwareOptions}. */
|
|
47
|
+
readonly ideId?: IdeId;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Resolve the active IDE id for a standards call. Order of precedence:
|
|
51
|
+
* 1. explicit `options.ideId`
|
|
52
|
+
* 2. `IdeRegistry.detect()` from `options.projectRoot`
|
|
53
|
+
* 3. `null` (no detected IDE — caller falls back to legacy)
|
|
54
|
+
*/
|
|
55
|
+
export declare function resolveStandardsIdeId(options: {
|
|
56
|
+
readonly projectRoot: string;
|
|
57
|
+
readonly ideId?: IdeId;
|
|
58
|
+
}): IdeId | null;
|
|
59
|
+
/**
|
|
60
|
+
* Run `peaks standards init` with IDE-aware dispatch.
|
|
61
|
+
*
|
|
62
|
+
* When the resolved IDE has a `standardsProfile`, the call still
|
|
63
|
+
* delegates to the existing `executeProjectStandardsInit` (the
|
|
64
|
+
* profile maps to the Claude Code path; future per-IDE writers
|
|
65
|
+
* plug in here). When the IDE is unregistered for the standards
|
|
66
|
+
* profile, the call delegates to the legacy path + emits a stderr
|
|
67
|
+
* warning.
|
|
68
|
+
*/
|
|
69
|
+
export declare function executeProjectStandardsInitIdeAware(options: ProjectStandardsIdeAwareOptions): ProjectStandardsInitResult;
|
|
70
|
+
/**
|
|
71
|
+
* Run `peaks standards update` with IDE-aware dispatch.
|
|
72
|
+
*
|
|
73
|
+
* Same dispatch rules as `executeProjectStandardsInitIdeAware`.
|
|
74
|
+
*/
|
|
75
|
+
export declare function executeProjectStandardsUpdateIdeAware(options: ProjectStandardsUpdateIdeAwareOptions): ProjectStandardsUpdateResult;
|
|
76
|
+
/**
|
|
77
|
+
* Test seam + integration-test helper: returns the resolved IDE id
|
|
78
|
+
* for the call, plus the active standards profile. Exported for
|
|
79
|
+
* the integration test in `tests/unit/standards/ide-aware-standards-service.test.ts`
|
|
80
|
+
* to assert the dispatch decision without running the full write.
|
|
81
|
+
*/
|
|
82
|
+
export declare function inspectStandardsDispatch(options: {
|
|
83
|
+
readonly projectRoot: string;
|
|
84
|
+
readonly ideId?: IdeId;
|
|
85
|
+
}): {
|
|
86
|
+
readonly ideId: IdeId | null;
|
|
87
|
+
readonly profile: ReturnType<typeof getStandardsProfile>;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Test seam: the resource-profile accessor exposes
|
|
91
|
+
* `detectAllResourceTargets` for callers that need to enumerate
|
|
92
|
+
* across all registered IDEs. Re-exported here for convenience.
|
|
93
|
+
*/
|
|
94
|
+
export { detectAllResourceTargets };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { detectAllResourceTargets, getStandardsProfile, } from '../ide/resource-profile.js';
|
|
2
|
+
import { executeProjectStandardsInit, executeProjectStandardsUpdate, } from './project-standards-service.js';
|
|
3
|
+
import { detectInstalledIde } from '../ide/ide-detector.js';
|
|
4
|
+
function warnUnregisteredIde(ideId, projectRoot) {
|
|
5
|
+
process.stderr.write(`peaks standards: IDE '${ideId}' has no standardsProfile declared; ` +
|
|
6
|
+
`falling back to the legacy Claude Code path (CLAUDE.md + .claude/rules/**) ` +
|
|
7
|
+
`for project '${projectRoot}'. This is a slice #011 follow-up gap; ` +
|
|
8
|
+
`see .peaks/memory/ide-adapter-resource-profile-framework.md.\n`);
|
|
9
|
+
}
|
|
10
|
+
function warnNoIdeDetected(projectRoot) {
|
|
11
|
+
process.stderr.write(`peaks standards: no IDE detected in '${projectRoot}'; ` +
|
|
12
|
+
`writing to the legacy Claude Code path (CLAUDE.md + .claude/rules/**). ` +
|
|
13
|
+
`Pass --ide <id> to bypass detection.\n`);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Resolve the active IDE id for a standards call. Order of precedence:
|
|
17
|
+
* 1. explicit `options.ideId`
|
|
18
|
+
* 2. `IdeRegistry.detect()` from `options.projectRoot`
|
|
19
|
+
* 3. `null` (no detected IDE — caller falls back to legacy)
|
|
20
|
+
*/
|
|
21
|
+
export function resolveStandardsIdeId(options) {
|
|
22
|
+
if (options.ideId !== undefined) {
|
|
23
|
+
return options.ideId;
|
|
24
|
+
}
|
|
25
|
+
return detectInstalledIde(options.projectRoot);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Run `peaks standards init` with IDE-aware dispatch.
|
|
29
|
+
*
|
|
30
|
+
* When the resolved IDE has a `standardsProfile`, the call still
|
|
31
|
+
* delegates to the existing `executeProjectStandardsInit` (the
|
|
32
|
+
* profile maps to the Claude Code path; future per-IDE writers
|
|
33
|
+
* plug in here). When the IDE is unregistered for the standards
|
|
34
|
+
* profile, the call delegates to the legacy path + emits a stderr
|
|
35
|
+
* warning.
|
|
36
|
+
*/
|
|
37
|
+
export function executeProjectStandardsInitIdeAware(options) {
|
|
38
|
+
const ideId = resolveStandardsIdeId(options);
|
|
39
|
+
if (ideId === null) {
|
|
40
|
+
warnNoIdeDetected(options.projectRoot);
|
|
41
|
+
return executeProjectStandardsInit(options);
|
|
42
|
+
}
|
|
43
|
+
const profile = getStandardsProfile(ideId);
|
|
44
|
+
if (profile === null) {
|
|
45
|
+
warnUnregisteredIde(ideId, options.projectRoot);
|
|
46
|
+
return executeProjectStandardsInit(options);
|
|
47
|
+
}
|
|
48
|
+
// Claude Code path: profile matches the legacy writer. Future per-IDE
|
|
49
|
+
// writers (markdown+frontmatter, multiple rule roots, etc.) plug in
|
|
50
|
+
// here by branching on `profile.format` / `profile.rulesDir`.
|
|
51
|
+
return executeProjectStandardsInit(options);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Run `peaks standards update` with IDE-aware dispatch.
|
|
55
|
+
*
|
|
56
|
+
* Same dispatch rules as `executeProjectStandardsInitIdeAware`.
|
|
57
|
+
*/
|
|
58
|
+
export function executeProjectStandardsUpdateIdeAware(options) {
|
|
59
|
+
const ideId = resolveStandardsIdeId(options);
|
|
60
|
+
if (ideId === null) {
|
|
61
|
+
warnNoIdeDetected(options.projectRoot);
|
|
62
|
+
return executeProjectStandardsUpdate(options);
|
|
63
|
+
}
|
|
64
|
+
const profile = getStandardsProfile(ideId);
|
|
65
|
+
if (profile === null) {
|
|
66
|
+
warnUnregisteredIde(ideId, options.projectRoot);
|
|
67
|
+
return executeProjectStandardsUpdate(options);
|
|
68
|
+
}
|
|
69
|
+
return executeProjectStandardsUpdate(options);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Test seam + integration-test helper: returns the resolved IDE id
|
|
73
|
+
* for the call, plus the active standards profile. Exported for
|
|
74
|
+
* the integration test in `tests/unit/standards/ide-aware-standards-service.test.ts`
|
|
75
|
+
* to assert the dispatch decision without running the full write.
|
|
76
|
+
*/
|
|
77
|
+
export function inspectStandardsDispatch(options) {
|
|
78
|
+
const ideId = resolveStandardsIdeId(options);
|
|
79
|
+
if (ideId === null) {
|
|
80
|
+
return { ideId: null, profile: null };
|
|
81
|
+
}
|
|
82
|
+
return { ideId, profile: getStandardsProfile(ideId) };
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Test seam: the resource-profile accessor exposes
|
|
86
|
+
* `detectAllResourceTargets` for callers that need to enumerate
|
|
87
|
+
* across all registered IDEs. Re-exported here for convenience.
|
|
88
|
+
*/
|
|
89
|
+
export { detectAllResourceTargets };
|
|
@@ -68,7 +68,7 @@ export type ProjectStandardsUpdateSummary = {
|
|
|
68
68
|
readonly reviewSuggestions: string[];
|
|
69
69
|
};
|
|
70
70
|
};
|
|
71
|
-
type ProjectStandardsInitOptions = {
|
|
71
|
+
export type ProjectStandardsInitOptions = {
|
|
72
72
|
readonly projectRoot: string;
|
|
73
73
|
readonly language?: string;
|
|
74
74
|
readonly apply?: boolean;
|
|
@@ -79,4 +79,3 @@ export declare function executeProjectStandardsInit(options: ProjectStandardsIni
|
|
|
79
79
|
export declare function executeProjectStandardsUpdate(options: ProjectStandardsInitOptions): ProjectStandardsUpdateResult;
|
|
80
80
|
export declare function summarizeProjectStandardsInitResult(result: ProjectStandardsInitResult): ProjectStandardsInitSummary;
|
|
81
81
|
export declare function summarizeProjectStandardsUpdateResult(result: ProjectStandardsUpdateResult): ProjectStandardsUpdateSummary;
|
|
82
|
-
export {};
|