sentinelayer-cli 0.1.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/README.md +996 -0
- package/bin/create-sentinelayer.js +5 -0
- package/bin/sentinelayer-cli.js +5 -0
- package/bin/sl.js +5 -0
- package/package.json +54 -0
- package/src/agents/jules/config/definition.js +209 -0
- package/src/agents/jules/config/system-prompt.js +175 -0
- package/src/agents/jules/error-intake.js +51 -0
- package/src/agents/jules/fix-cycle.js +377 -0
- package/src/agents/jules/loop.js +367 -0
- package/src/agents/jules/pulse.js +319 -0
- package/src/agents/jules/stream.js +186 -0
- package/src/agents/jules/swarm/file-scanner.js +74 -0
- package/src/agents/jules/swarm/index.js +11 -0
- package/src/agents/jules/swarm/orchestrator.js +362 -0
- package/src/agents/jules/swarm/pattern-hunter.js +123 -0
- package/src/agents/jules/swarm/sub-agent.js +308 -0
- package/src/agents/jules/tools/auth-audit.js +222 -0
- package/src/agents/jules/tools/dispatch.js +327 -0
- package/src/agents/jules/tools/file-edit.js +180 -0
- package/src/agents/jules/tools/file-read.js +100 -0
- package/src/agents/jules/tools/frontend-analyze.js +570 -0
- package/src/agents/jules/tools/glob.js +168 -0
- package/src/agents/jules/tools/grep.js +228 -0
- package/src/agents/jules/tools/index.js +29 -0
- package/src/agents/jules/tools/path-guards.js +161 -0
- package/src/agents/jules/tools/runtime-audit.js +409 -0
- package/src/agents/jules/tools/shell.js +383 -0
- package/src/ai/aidenid.js +945 -0
- package/src/ai/client.js +508 -0
- package/src/ai/domain-target-store.js +268 -0
- package/src/ai/identity-store.js +270 -0
- package/src/ai/site-store.js +145 -0
- package/src/audit/agents/architecture.js +180 -0
- package/src/audit/agents/compliance.js +179 -0
- package/src/audit/agents/documentation.js +165 -0
- package/src/audit/agents/performance.js +145 -0
- package/src/audit/agents/security.js +215 -0
- package/src/audit/agents/testing.js +172 -0
- package/src/audit/orchestrator.js +557 -0
- package/src/audit/package.js +204 -0
- package/src/audit/registry.js +284 -0
- package/src/audit/replay.js +103 -0
- package/src/auth/http.js +113 -0
- package/src/auth/service.js +848 -0
- package/src/auth/session-store.js +345 -0
- package/src/cli.js +244 -0
- package/src/commands/ai/identity-lifecycle.js +1337 -0
- package/src/commands/ai/provision-governance.js +1246 -0
- package/src/commands/ai/shared.js +147 -0
- package/src/commands/ai.js +11 -0
- package/src/commands/apply.js +19 -0
- package/src/commands/audit.js +1147 -0
- package/src/commands/auth.js +366 -0
- package/src/commands/chat.js +191 -0
- package/src/commands/config.js +184 -0
- package/src/commands/cost.js +311 -0
- package/src/commands/daemon/core.js +850 -0
- package/src/commands/daemon/extended.js +1048 -0
- package/src/commands/daemon/shared.js +213 -0
- package/src/commands/daemon.js +11 -0
- package/src/commands/guide.js +174 -0
- package/src/commands/ingest.js +58 -0
- package/src/commands/init.js +55 -0
- package/src/commands/legacy-args.js +30 -0
- package/src/commands/mcp.js +404 -0
- package/src/commands/omargate.js +21 -0
- package/src/commands/persona.js +27 -0
- package/src/commands/plugin.js +260 -0
- package/src/commands/policy.js +132 -0
- package/src/commands/prompt.js +238 -0
- package/src/commands/review.js +704 -0
- package/src/commands/scan.js +788 -0
- package/src/commands/spec.js +716 -0
- package/src/commands/swarm.js +651 -0
- package/src/commands/telemetry.js +202 -0
- package/src/commands/watch.js +510 -0
- package/src/config/agent-dictionary.js +182 -0
- package/src/config/io.js +56 -0
- package/src/config/paths.js +18 -0
- package/src/config/schema.js +55 -0
- package/src/config/service.js +184 -0
- package/src/cost/budget.js +235 -0
- package/src/cost/history.js +188 -0
- package/src/cost/tracker.js +171 -0
- package/src/daemon/artifact-lineage.js +534 -0
- package/src/daemon/assignment-ledger.js +770 -0
- package/src/daemon/ast-parser-layer.js +258 -0
- package/src/daemon/budget-governor.js +633 -0
- package/src/daemon/callgraph-overlay.js +646 -0
- package/src/daemon/error-worker.js +626 -0
- package/src/daemon/hybrid-mapper.js +929 -0
- package/src/daemon/jira-lifecycle.js +632 -0
- package/src/daemon/operator-control.js +657 -0
- package/src/daemon/reliability-lane.js +471 -0
- package/src/daemon/watchdog.js +971 -0
- package/src/guide/generator.js +316 -0
- package/src/ingest/engine.js +918 -0
- package/src/legacy-cli.js +2435 -0
- package/src/mcp/registry.js +695 -0
- package/src/memory/blackboard.js +301 -0
- package/src/memory/retrieval.js +581 -0
- package/src/plugin/manifest.js +553 -0
- package/src/policy/packs.js +144 -0
- package/src/prompt/generator.js +106 -0
- package/src/review/ai-review.js +669 -0
- package/src/review/local-review.js +1284 -0
- package/src/review/replay.js +235 -0
- package/src/review/report.js +664 -0
- package/src/review/spec-binding.js +487 -0
- package/src/scan/generator.js +351 -0
- package/src/spec/generator.js +519 -0
- package/src/spec/regenerate.js +237 -0
- package/src/spec/templates.js +91 -0
- package/src/swarm/dashboard.js +247 -0
- package/src/swarm/factory.js +363 -0
- package/src/swarm/pentest.js +934 -0
- package/src/swarm/registry.js +419 -0
- package/src/swarm/report.js +158 -0
- package/src/swarm/runtime.js +576 -0
- package/src/swarm/scenario-dsl.js +272 -0
- package/src/telemetry/ledger.js +302 -0
- package/src/ui/markdown.js +220 -0
- package/src/ui/progress.js +100 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import pc from "picocolors";
|
|
2
|
+
|
|
3
|
+
import { getBudgetHealthColor } from "../../daemon/operator-control.js";
|
|
4
|
+
|
|
5
|
+
// Shared helper utilities for daemon command modules.
|
|
6
|
+
|
|
7
|
+
export function shouldEmitJson(options, command) {
|
|
8
|
+
const local = Boolean(options && options.json);
|
|
9
|
+
const globalFromCommand =
|
|
10
|
+
command && command.optsWithGlobals ? Boolean(command.optsWithGlobals().json) : false;
|
|
11
|
+
return local || globalFromCommand;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function parsePositiveInteger(rawValue, field, fallbackValue) {
|
|
15
|
+
if (rawValue === undefined || rawValue === null || String(rawValue).trim() === "") {
|
|
16
|
+
return fallbackValue;
|
|
17
|
+
}
|
|
18
|
+
const normalized = Number(rawValue);
|
|
19
|
+
if (!Number.isFinite(normalized) || normalized <= 0) {
|
|
20
|
+
throw new Error(`${field} must be a positive integer.`);
|
|
21
|
+
}
|
|
22
|
+
return Math.floor(normalized);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function parseCsv(rawValue) {
|
|
26
|
+
if (rawValue === undefined || rawValue === null || String(rawValue).trim() === "") {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
return String(rawValue)
|
|
30
|
+
.split(",")
|
|
31
|
+
.map((item) => item.trim())
|
|
32
|
+
.filter(Boolean);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function parseMetadata(rawValue) {
|
|
36
|
+
if (!rawValue) {
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
const parsed = JSON.parse(String(rawValue));
|
|
40
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
41
|
+
throw new Error("metadataJson must parse to an object.");
|
|
42
|
+
}
|
|
43
|
+
return parsed;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function parseBoolean(rawValue, fallbackValue) {
|
|
47
|
+
if (rawValue === undefined || rawValue === null || String(rawValue).trim() === "") {
|
|
48
|
+
return fallbackValue;
|
|
49
|
+
}
|
|
50
|
+
const normalized = String(rawValue).trim().toLowerCase();
|
|
51
|
+
if (normalized === "true" || normalized === "1" || normalized === "yes") {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
if (normalized === "false" || normalized === "0" || normalized === "no") {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
throw new Error("Value must be true/false.");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function printQueueSummary(payload) {
|
|
61
|
+
console.log(pc.bold("OMAR error daemon queue"));
|
|
62
|
+
console.log(pc.gray(`Queue: ${payload.queuePath}`));
|
|
63
|
+
console.log(pc.gray(`State: ${payload.statePath}`));
|
|
64
|
+
console.log(
|
|
65
|
+
pc.gray(
|
|
66
|
+
`visible=${payload.visibleCount} total=${payload.totalCount} stream_offset=${payload.workerState?.streamOffset ?? 0}`
|
|
67
|
+
)
|
|
68
|
+
);
|
|
69
|
+
for (const item of payload.items) {
|
|
70
|
+
console.log(
|
|
71
|
+
`- ${item.workItemId} | ${item.severity} | ${item.status} | occurrences=${item.occurrenceCount} | ${item.service} ${item.endpoint}`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function printAssignmentSummary(payload) {
|
|
77
|
+
console.log(pc.bold("OMAR assignment ledger"));
|
|
78
|
+
console.log(pc.gray(`Ledger: ${payload.ledgerPath}`));
|
|
79
|
+
console.log(pc.gray(`Queue: ${payload.queuePath}`));
|
|
80
|
+
console.log(pc.gray(`Events: ${payload.eventsPath}`));
|
|
81
|
+
console.log(pc.gray(`visible=${payload.visibleCount} total=${payload.totalCount}`));
|
|
82
|
+
for (const assignment of payload.assignments) {
|
|
83
|
+
console.log(
|
|
84
|
+
`- ${assignment.workItemId} | ${assignment.status} | ${assignment.assignedAgentIdentity || "unassigned"} | stage=${assignment.stage} | lease_expires=${assignment.leaseExpiresAt || "n/a"}`
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function printJiraSummary(payload) {
|
|
90
|
+
console.log(pc.bold("OMAR Jira lifecycle"));
|
|
91
|
+
console.log(pc.gray(`Lifecycle: ${payload.lifecyclePath}`));
|
|
92
|
+
console.log(pc.gray(`Events: ${payload.eventsPath}`));
|
|
93
|
+
console.log(pc.gray(`visible=${payload.visibleCount} total=${payload.totalCount}`));
|
|
94
|
+
for (const issue of payload.issues) {
|
|
95
|
+
console.log(
|
|
96
|
+
`- ${issue.issueKey} | ${issue.status} | work_item=${issue.workItemId} | assignee=${issue.assignee || "n/a"}`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function printBudgetSummary(payload) {
|
|
102
|
+
console.log(pc.bold("OMAR budget governor"));
|
|
103
|
+
console.log(pc.gray(`State: ${payload.budgetStatePath}`));
|
|
104
|
+
console.log(pc.gray(`Events: ${payload.budgetEventsPath}`));
|
|
105
|
+
console.log(pc.gray(`visible=${payload.visibleCount} total=${payload.totalCount}`));
|
|
106
|
+
for (const record of payload.records) {
|
|
107
|
+
const stopCodes = Array.isArray(record.stopReasons)
|
|
108
|
+
? record.stopReasons.map((item) => item.code).join(", ")
|
|
109
|
+
: "";
|
|
110
|
+
console.log(
|
|
111
|
+
`- ${record.workItemId} | ${record.lifecycleState} | action=${record.lastAction || "NONE"} | quarantine_until=${record.quarantineUntil || "n/a"}${stopCodes ? ` | stops=${stopCodes}` : ""}`
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function formatDurationSeconds(seconds) {
|
|
117
|
+
const normalized = Number(seconds);
|
|
118
|
+
if (!Number.isFinite(normalized) || normalized < 0) {
|
|
119
|
+
return "n/a";
|
|
120
|
+
}
|
|
121
|
+
const total = Math.floor(normalized);
|
|
122
|
+
const hours = Math.floor(total / 3600);
|
|
123
|
+
const minutes = Math.floor((total % 3600) / 60);
|
|
124
|
+
const secs = total % 60;
|
|
125
|
+
if (hours > 0) {
|
|
126
|
+
return `${hours}h ${minutes}m ${secs}s`;
|
|
127
|
+
}
|
|
128
|
+
if (minutes > 0) {
|
|
129
|
+
return `${minutes}m ${secs}s`;
|
|
130
|
+
}
|
|
131
|
+
return `${secs}s`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function colorizeBudgetHealth(value) {
|
|
135
|
+
const normalized = getBudgetHealthColor(value);
|
|
136
|
+
if (normalized === "RED") {
|
|
137
|
+
return pc.red(normalized);
|
|
138
|
+
}
|
|
139
|
+
if (normalized === "YELLOW") {
|
|
140
|
+
return pc.yellow(normalized);
|
|
141
|
+
}
|
|
142
|
+
return pc.green(normalized);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function printControlPlaneSummary(payload) {
|
|
146
|
+
console.log(pc.bold("OMAR operator control plane"));
|
|
147
|
+
console.log(pc.gray(`State: ${payload.operatorStatePath}`));
|
|
148
|
+
console.log(pc.gray(`Events: ${payload.operatorEventsPath}`));
|
|
149
|
+
console.log(pc.gray(`Snapshot: ${payload.runPath}`));
|
|
150
|
+
console.log(
|
|
151
|
+
pc.gray(
|
|
152
|
+
`visible=${payload.visibleWorkItems} total_queue=${payload.totalQueueItems} active_agents=${payload.agentRoster.length}`
|
|
153
|
+
)
|
|
154
|
+
);
|
|
155
|
+
for (const row of payload.workItems) {
|
|
156
|
+
console.log(
|
|
157
|
+
`- ${row.workItemId} | ${row.severity} | ${row.workItemStatus} | agent=${row.assignedAgentIdentity || "unassigned"} | budget=${colorizeBudgetHealth(row.budgetHealthColor)} | elapsed=${formatDurationSeconds(row.sessionElapsedSeconds)} | idle=${formatDurationSeconds(row.sessionIdleSeconds)} | jira=${row.jiraIssueKey || "n/a"}`
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
if (payload.agentRoster.length > 0) {
|
|
161
|
+
console.log(pc.bold("Agent roster"));
|
|
162
|
+
for (const agent of payload.agentRoster) {
|
|
163
|
+
console.log(
|
|
164
|
+
`- ${agent.agentIdentity} | work_items=${agent.workItemCount} | active=${agent.activeWorkItemCount} | blocked=${agent.blockedCount} | squashed=${agent.squashedCount} | longest_session=${formatDurationSeconds(agent.maxSessionElapsedSeconds)}`
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function printLineageSummary(payload) {
|
|
171
|
+
console.log(pc.bold("OMAR artifact lineage"));
|
|
172
|
+
console.log(pc.gray(`Index: ${payload.indexPath}`));
|
|
173
|
+
console.log(pc.gray(`Events: ${payload.eventPath}`));
|
|
174
|
+
console.log(
|
|
175
|
+
pc.gray(
|
|
176
|
+
`visible=${payload.visibleCount} total=${payload.totalCount} lineage_run=${payload.lineageRunId || "n/a"}`
|
|
177
|
+
)
|
|
178
|
+
);
|
|
179
|
+
for (const item of payload.workItems) {
|
|
180
|
+
console.log(
|
|
181
|
+
`- ${item.workItemId} | ${item.severity} | ${item.workItemStatus} | agent=${item.links?.agentIdentity || "unassigned"} | jira=${item.links?.jiraIssueKey || "n/a"} | budget=${item.links?.budgetLifecycleState || "WITHIN_BUDGET"} | operator_snapshot=${item.links?.latestOperatorSnapshotRunId || "n/a"}`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function printHybridMapSummary(payload) {
|
|
187
|
+
console.log(pc.bold("OMAR hybrid mapping overlay"));
|
|
188
|
+
console.log(pc.gray(`Index: ${payload.mapIndexPath}`));
|
|
189
|
+
console.log(pc.gray(`Events: ${payload.mapEventsPath}`));
|
|
190
|
+
console.log(pc.gray(`visible=${payload.visibleCount} total=${payload.totalCount}`));
|
|
191
|
+
for (const map of payload.maps) {
|
|
192
|
+
console.log(
|
|
193
|
+
`- ${map.workItemId} | run=${map.runId} | status=${map.status || "n/a"} | seeds=${map.deterministicSeedCount || 0} | scoped=${map.scopedFileCount || 0}`
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function printReliabilitySummary(payload) {
|
|
199
|
+
console.log(pc.bold("OMAR reliability lane"));
|
|
200
|
+
console.log(pc.gray(`Config: ${payload.configPath}`));
|
|
201
|
+
console.log(pc.gray(`Billboard: ${payload.billboardPath}`));
|
|
202
|
+
console.log(pc.gray(`Events: ${payload.eventsPath}`));
|
|
203
|
+
console.log(
|
|
204
|
+
pc.gray(
|
|
205
|
+
`maintenance=${payload.billboard?.enabled ? "ON" : "OFF"} checks=${payload.config?.checks?.length || 0} recent_runs=${payload.recentRuns?.length || 0}`
|
|
206
|
+
)
|
|
207
|
+
);
|
|
208
|
+
for (const run of payload.recentRuns || []) {
|
|
209
|
+
console.log(
|
|
210
|
+
`- ${run.runId} | ${run.overallStatus} | failures=${run.failureCount} | ${run.generatedAt}`
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { registerDaemonCoreCommands } from "./daemon/core.js";
|
|
2
|
+
import { registerDaemonExtendedCommands } from "./daemon/extended.js";
|
|
3
|
+
|
|
4
|
+
export function registerDaemonCommand(program) {
|
|
5
|
+
const daemon = program
|
|
6
|
+
.command("daemon")
|
|
7
|
+
.description("OMAR daemon controls for error-event intake and routed queue management");
|
|
8
|
+
|
|
9
|
+
registerDaemonCoreCommands(daemon);
|
|
10
|
+
registerDaemonExtendedCommands(daemon);
|
|
11
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import fsp from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import pc from "picocolors";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
defaultGuideExportFileName,
|
|
9
|
+
generateBuildGuide,
|
|
10
|
+
renderGuideExport,
|
|
11
|
+
SUPPORTED_GUIDE_EXPORT_FORMATS,
|
|
12
|
+
} from "../guide/generator.js";
|
|
13
|
+
import { renderTerminalMarkdown } from "../ui/markdown.js";
|
|
14
|
+
|
|
15
|
+
function shouldEmitJson(options, command) {
|
|
16
|
+
const local = Boolean(options && options.json);
|
|
17
|
+
const globalFromCommand =
|
|
18
|
+
command && command.optsWithGlobals ? Boolean(command.optsWithGlobals().json) : false;
|
|
19
|
+
return local || globalFromCommand;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function resolveSpecPath(targetPath, explicitSpecFile) {
|
|
23
|
+
const explicit = String(explicitSpecFile || "").trim();
|
|
24
|
+
if (explicit) {
|
|
25
|
+
return path.resolve(targetPath, explicit);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const candidates = [path.join(targetPath, "SPEC.md"), path.join(targetPath, "docs", "spec.md")];
|
|
29
|
+
const found = candidates.find((candidate) => fs.existsSync(candidate));
|
|
30
|
+
if (!found) {
|
|
31
|
+
throw new Error("No spec file found. Provide --spec-file or generate SPEC.md first.");
|
|
32
|
+
}
|
|
33
|
+
return found;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function resolveExportFormat(rawFormat) {
|
|
37
|
+
const normalized = String(rawFormat || "").trim().toLowerCase();
|
|
38
|
+
if (!SUPPORTED_GUIDE_EXPORT_FORMATS.includes(normalized)) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`Unsupported export format '${rawFormat}'. Use one of: ${SUPPORTED_GUIDE_EXPORT_FORMATS.join(", ")}`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return normalized;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function registerGuideCommand(program) {
|
|
47
|
+
const guide = program.command("guide").description("Generate and export phase-by-phase build guides");
|
|
48
|
+
|
|
49
|
+
guide
|
|
50
|
+
.command("generate")
|
|
51
|
+
.description("Generate BUILD_GUIDE.md from SPEC content")
|
|
52
|
+
.option("--path <path>", "Target workspace path", ".")
|
|
53
|
+
.option("--spec-file <path>", "Spec file path relative to --path")
|
|
54
|
+
.option("--output-file <path>", "Output guide path relative to --path", "BUILD_GUIDE.md")
|
|
55
|
+
.option("--json", "Emit machine-readable output")
|
|
56
|
+
.action(async (options, command) => {
|
|
57
|
+
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
58
|
+
const outputPath = path.resolve(targetPath, String(options.outputFile || "BUILD_GUIDE.md").trim());
|
|
59
|
+
const specPath = resolveSpecPath(targetPath, options.specFile);
|
|
60
|
+
const specMarkdown = await fsp.readFile(specPath, "utf-8");
|
|
61
|
+
|
|
62
|
+
const guideDoc = generateBuildGuide({
|
|
63
|
+
specMarkdown,
|
|
64
|
+
projectPath: targetPath,
|
|
65
|
+
specPath,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
await fsp.mkdir(path.dirname(outputPath), { recursive: true });
|
|
69
|
+
await fsp.writeFile(outputPath, `${guideDoc.markdown.trimEnd()}\n`, "utf-8");
|
|
70
|
+
|
|
71
|
+
const payload = {
|
|
72
|
+
command: "guide generate",
|
|
73
|
+
targetPath,
|
|
74
|
+
specPath,
|
|
75
|
+
outputPath,
|
|
76
|
+
phases: guideDoc.phases.map((phase) => ({
|
|
77
|
+
title: phase.title,
|
|
78
|
+
effort: phase.effort.label,
|
|
79
|
+
dependencies: phase.dependencies,
|
|
80
|
+
})),
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
if (shouldEmitJson(options, command)) {
|
|
84
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
console.log(pc.bold("Build guide generated"));
|
|
89
|
+
console.log(pc.gray(`Spec: ${specPath}`));
|
|
90
|
+
console.log(pc.gray(`Output: ${outputPath}`));
|
|
91
|
+
console.log(pc.gray(`Phases: ${guideDoc.phases.length}`));
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
guide
|
|
95
|
+
.command("export")
|
|
96
|
+
.description("Export build-guide phases into issue tracker-friendly format")
|
|
97
|
+
.requiredOption(
|
|
98
|
+
"--format <type>",
|
|
99
|
+
`Export format (${SUPPORTED_GUIDE_EXPORT_FORMATS.join("|")})`
|
|
100
|
+
)
|
|
101
|
+
.option("--path <path>", "Target workspace path", ".")
|
|
102
|
+
.option("--spec-file <path>", "Spec file path relative to --path")
|
|
103
|
+
.option("--output-file <path>", "Output export file path relative to --path")
|
|
104
|
+
.option("--json", "Emit machine-readable output")
|
|
105
|
+
.action(async (options, command) => {
|
|
106
|
+
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
107
|
+
const format = resolveExportFormat(options.format);
|
|
108
|
+
const outputName =
|
|
109
|
+
String(options.outputFile || "").trim() || defaultGuideExportFileName(format);
|
|
110
|
+
const outputPath = path.resolve(targetPath, outputName);
|
|
111
|
+
const specPath = resolveSpecPath(targetPath, options.specFile);
|
|
112
|
+
const specMarkdown = await fsp.readFile(specPath, "utf-8");
|
|
113
|
+
|
|
114
|
+
const guideDoc = generateBuildGuide({
|
|
115
|
+
specMarkdown,
|
|
116
|
+
projectPath: targetPath,
|
|
117
|
+
specPath,
|
|
118
|
+
});
|
|
119
|
+
const exportBody = renderGuideExport({ format, guide: guideDoc });
|
|
120
|
+
|
|
121
|
+
await fsp.mkdir(path.dirname(outputPath), { recursive: true });
|
|
122
|
+
await fsp.writeFile(outputPath, `${exportBody.trimEnd()}\n`, "utf-8");
|
|
123
|
+
|
|
124
|
+
const payload = {
|
|
125
|
+
command: "guide export",
|
|
126
|
+
format,
|
|
127
|
+
targetPath,
|
|
128
|
+
specPath,
|
|
129
|
+
outputPath,
|
|
130
|
+
issueCount: guideDoc.tickets.length,
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
if (shouldEmitJson(options, command)) {
|
|
134
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
console.log(pc.bold("Build guide export generated"));
|
|
139
|
+
console.log(pc.gray(`Format: ${format}`));
|
|
140
|
+
console.log(pc.gray(`Output: ${outputPath}`));
|
|
141
|
+
console.log(pc.gray(`Issues: ${guideDoc.tickets.length}`));
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
guide
|
|
145
|
+
.command("show")
|
|
146
|
+
.description("Render an existing BUILD_GUIDE artifact in terminal markdown")
|
|
147
|
+
.option("--path <path>", "Target workspace path", ".")
|
|
148
|
+
.option("--file <path>", "Guide file path relative to --path", "BUILD_GUIDE.md")
|
|
149
|
+
.option("--plain", "Disable terminal markdown styling")
|
|
150
|
+
.option("--json", "Emit machine-readable output")
|
|
151
|
+
.action(async (options, command) => {
|
|
152
|
+
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
153
|
+
const guidePath = path.resolve(targetPath, String(options.file || "BUILD_GUIDE.md").trim());
|
|
154
|
+
if (!fs.existsSync(guidePath)) {
|
|
155
|
+
throw new Error(`Guide artifact not found: ${guidePath}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const markdown = await fsp.readFile(guidePath, "utf-8");
|
|
159
|
+
const payload = {
|
|
160
|
+
command: "guide show",
|
|
161
|
+
guidePath,
|
|
162
|
+
lineCount: markdown.split(/\r?\n/).length,
|
|
163
|
+
preview: markdown,
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
if (shouldEmitJson(options, command)) {
|
|
167
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
console.log(renderTerminalMarkdown(markdown, { plain: Boolean(options.plain) }));
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
import pc from "picocolors";
|
|
4
|
+
|
|
5
|
+
import { formatIngestSummary, generateCodebaseIngest } from "../ingest/engine.js";
|
|
6
|
+
|
|
7
|
+
function shouldEmitJson(options, command) {
|
|
8
|
+
const local = Boolean(options && options.json);
|
|
9
|
+
const globalFromCommand =
|
|
10
|
+
command && command.optsWithGlobals ? Boolean(command.optsWithGlobals().json) : false;
|
|
11
|
+
return local || globalFromCommand;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function registerIngestCommand(program) {
|
|
15
|
+
const ingest = program
|
|
16
|
+
.command("ingest")
|
|
17
|
+
.description("Run deterministic codebase ingest and mapping");
|
|
18
|
+
|
|
19
|
+
ingest
|
|
20
|
+
.command("map")
|
|
21
|
+
.description("Generate CODEBASE_INGEST.json with stack/risk hints")
|
|
22
|
+
.option("--path <path>", "Target repository path", ".")
|
|
23
|
+
.option("--output-file <path>", "Explicit output file path relative to --path")
|
|
24
|
+
.option("--output-dir <path>", "Artifact root used when output file is not provided")
|
|
25
|
+
.option("--json", "Emit machine-readable output")
|
|
26
|
+
.action(async (options, command) => {
|
|
27
|
+
const targetPath = path.resolve(process.cwd(), String(options.path || "."));
|
|
28
|
+
const result = await generateCodebaseIngest({
|
|
29
|
+
rootPath: targetPath,
|
|
30
|
+
outputFile: options.outputFile,
|
|
31
|
+
outputDir: options.outputDir,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const emitJson = shouldEmitJson(options, command);
|
|
35
|
+
if (emitJson) {
|
|
36
|
+
console.log(
|
|
37
|
+
JSON.stringify(
|
|
38
|
+
{
|
|
39
|
+
command: "ingest map",
|
|
40
|
+
targetPath,
|
|
41
|
+
outputPath: result.outputPath,
|
|
42
|
+
summary: result.ingest.summary,
|
|
43
|
+
frameworks: result.ingest.frameworks,
|
|
44
|
+
entryPoints: result.ingest.entryPoints,
|
|
45
|
+
riskSurfaces: result.ingest.riskSurfaces,
|
|
46
|
+
},
|
|
47
|
+
null,
|
|
48
|
+
2
|
|
49
|
+
)
|
|
50
|
+
);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log(pc.bold("Codebase ingest completed"));
|
|
55
|
+
console.log(pc.gray(`Output: ${result.outputPath}`));
|
|
56
|
+
console.log(formatIngestSummary(result.ingest));
|
|
57
|
+
});
|
|
58
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
|
|
3
|
+
import { loadConfig } from "../config/service.js";
|
|
4
|
+
|
|
5
|
+
function applyConfigEnvDefaults(resolvedConfig) {
|
|
6
|
+
if (!process.env.SENTINELAYER_API_URL && resolvedConfig.apiUrl) {
|
|
7
|
+
process.env.SENTINELAYER_API_URL = resolvedConfig.apiUrl;
|
|
8
|
+
}
|
|
9
|
+
if (!process.env.SENTINELAYER_WEB_URL && resolvedConfig.webUrl) {
|
|
10
|
+
process.env.SENTINELAYER_WEB_URL = resolvedConfig.webUrl;
|
|
11
|
+
}
|
|
12
|
+
if (!process.env.SENTINELAYER_TOKEN && resolvedConfig.sentinelayerToken) {
|
|
13
|
+
process.env.SENTINELAYER_TOKEN = resolvedConfig.sentinelayerToken;
|
|
14
|
+
}
|
|
15
|
+
if (!process.env.OPENAI_API_KEY && resolvedConfig.openaiApiKey) {
|
|
16
|
+
process.env.OPENAI_API_KEY = resolvedConfig.openaiApiKey;
|
|
17
|
+
}
|
|
18
|
+
if (!process.env.ANTHROPIC_API_KEY && resolvedConfig.anthropicApiKey) {
|
|
19
|
+
process.env.ANTHROPIC_API_KEY = resolvedConfig.anthropicApiKey;
|
|
20
|
+
}
|
|
21
|
+
if (!process.env.GOOGLE_API_KEY && resolvedConfig.googleApiKey) {
|
|
22
|
+
process.env.GOOGLE_API_KEY = resolvedConfig.googleApiKey;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function registerInitCommand(program, invokeLegacy) {
|
|
27
|
+
program
|
|
28
|
+
.command("init [projectName]")
|
|
29
|
+
.description("Run scaffold/auth generation flow")
|
|
30
|
+
.option("--non-interactive", "Disable prompts and require interview payload")
|
|
31
|
+
.option("--interview-file <path>", "Load interview JSON from file")
|
|
32
|
+
.option("--skip-browser-open", "Do not auto-open browser during auth")
|
|
33
|
+
.action(async (projectName, options) => {
|
|
34
|
+
const config = await loadConfig();
|
|
35
|
+
applyConfigEnvDefaults(config.resolved);
|
|
36
|
+
|
|
37
|
+
const legacyArgs = [];
|
|
38
|
+
const normalizedProjectName = String(projectName || "").trim();
|
|
39
|
+
if (normalizedProjectName) {
|
|
40
|
+
legacyArgs.push(normalizedProjectName);
|
|
41
|
+
}
|
|
42
|
+
if (options.nonInteractive) {
|
|
43
|
+
legacyArgs.push("--non-interactive");
|
|
44
|
+
}
|
|
45
|
+
const interviewFile = String(options.interviewFile || "").trim();
|
|
46
|
+
if (interviewFile) {
|
|
47
|
+
legacyArgs.push("--interview-file", interviewFile);
|
|
48
|
+
}
|
|
49
|
+
if (options.skipBrowserOpen) {
|
|
50
|
+
legacyArgs.push("--skip-browser-open");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
await invokeLegacy(legacyArgs);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
function wantsJsonOutput(commandOptions, command) {
|
|
2
|
+
const local = Boolean(commandOptions && commandOptions.json);
|
|
3
|
+
const globalFromCommand =
|
|
4
|
+
command && command.optsWithGlobals ? Boolean(command.optsWithGlobals().json) : false;
|
|
5
|
+
return local || globalFromCommand;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function appendPathFlag(args, maybePath) {
|
|
9
|
+
const value = String(maybePath || "").trim();
|
|
10
|
+
if (value) {
|
|
11
|
+
args.push("--path", value);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function appendOutputDirFlag(args, maybeOutputDir) {
|
|
16
|
+
const value = String(maybeOutputDir || "").trim();
|
|
17
|
+
if (value) {
|
|
18
|
+
args.push("--output-dir", value);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function buildLegacyArgs(baseArgs, { commandOptions = {}, command } = {}) {
|
|
23
|
+
const args = [...baseArgs];
|
|
24
|
+
appendPathFlag(args, commandOptions.path);
|
|
25
|
+
appendOutputDirFlag(args, commandOptions.outputDir);
|
|
26
|
+
if (wantsJsonOutput(commandOptions, command)) {
|
|
27
|
+
args.push("--json");
|
|
28
|
+
}
|
|
29
|
+
return args;
|
|
30
|
+
}
|