sdd-cli 0.1.19 → 0.1.21
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 +41 -0
- package/dist/cli.js +58 -3
- package/dist/commands/ai-exec.js +3 -2
- package/dist/commands/ai-status.js +2 -1
- package/dist/commands/doctor.d.ts +1 -1
- package/dist/commands/doctor.js +218 -10
- package/dist/commands/gen-architecture.js +6 -5
- package/dist/commands/gen-best-practices.js +6 -5
- package/dist/commands/gen-functional-spec.js +6 -5
- package/dist/commands/gen-project-readme.js +6 -5
- package/dist/commands/gen-technical-spec.js +6 -5
- package/dist/commands/gen-utils.js +2 -1
- package/dist/commands/hello.js +27 -9
- package/dist/commands/import-issue.js +3 -2
- package/dist/commands/import-jira.d.ts +1 -0
- package/dist/commands/import-jira.js +127 -0
- package/dist/commands/learn-deliver.js +6 -5
- package/dist/commands/learn-refine.js +8 -7
- package/dist/commands/learn-start.js +3 -2
- package/dist/commands/list.js +19 -4
- package/dist/commands/pr-audit.js +6 -5
- package/dist/commands/pr-bridge-check.d.ts +1 -0
- package/dist/commands/pr-bridge-check.js +88 -0
- package/dist/commands/pr-bridge.d.ts +1 -0
- package/dist/commands/pr-bridge.js +124 -0
- package/dist/commands/pr-finish.js +6 -5
- package/dist/commands/pr-report.js +6 -5
- package/dist/commands/pr-respond.js +7 -6
- package/dist/commands/pr-risk.d.ts +1 -0
- package/dist/commands/pr-risk.js +112 -0
- package/dist/commands/pr-start.js +4 -3
- package/dist/commands/quickstart.js +10 -1
- package/dist/commands/req-archive.js +4 -3
- package/dist/commands/req-create.js +8 -7
- package/dist/commands/req-export.js +4 -3
- package/dist/commands/req-finish.js +9 -8
- package/dist/commands/req-lint.js +16 -6
- package/dist/commands/req-list.js +4 -3
- package/dist/commands/req-plan.js +12 -11
- package/dist/commands/req-refine.js +9 -8
- package/dist/commands/req-report.js +10 -9
- package/dist/commands/req-start.js +10 -9
- package/dist/commands/req-status.js +4 -3
- package/dist/commands/route.js +19 -4
- package/dist/commands/scope-list.d.ts +1 -0
- package/dist/commands/scope-list.js +16 -0
- package/dist/commands/scope-status.d.ts +1 -0
- package/dist/commands/scope-status.js +33 -0
- package/dist/commands/status.js +16 -7
- package/dist/commands/test-plan.js +6 -5
- package/dist/context/flags.d.ts +2 -0
- package/dist/context/flags.js +9 -1
- package/dist/errors.d.ts +2 -0
- package/dist/errors.js +10 -0
- package/dist/paths.js +4 -0
- package/dist/telemetry/local-metrics.d.ts +2 -0
- package/dist/telemetry/local-metrics.js +85 -0
- package/dist/workspace/index.d.ts +4 -0
- package/dist/workspace/index.js +129 -27
- package/package.json +24 -2
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.recordCommandMetric = recordCommandMetric;
|
|
7
|
+
exports.recordActivationMetric = recordActivationMetric;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const flags_1 = require("../context/flags");
|
|
11
|
+
const index_1 = require("../workspace/index");
|
|
12
|
+
function isEnabled() {
|
|
13
|
+
const flags = (0, flags_1.getFlags)();
|
|
14
|
+
return Boolean(flags.metricsLocal) || process.env.SDD_METRICS_LOCAL === "1";
|
|
15
|
+
}
|
|
16
|
+
function metricsPath() {
|
|
17
|
+
const workspace = (0, index_1.getWorkspaceInfo)();
|
|
18
|
+
(0, index_1.ensureWorkspace)(workspace);
|
|
19
|
+
const dir = path_1.default.join(workspace.root, "metrics");
|
|
20
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
21
|
+
return path_1.default.join(dir, "local-metrics.json");
|
|
22
|
+
}
|
|
23
|
+
function loadSnapshot(filePath) {
|
|
24
|
+
if (!fs_1.default.existsSync(filePath)) {
|
|
25
|
+
const now = new Date().toISOString();
|
|
26
|
+
return {
|
|
27
|
+
firstSeenAt: now,
|
|
28
|
+
lastSeenAt: now,
|
|
29
|
+
commandCounts: {},
|
|
30
|
+
activation: { started: 0, completed: 0 },
|
|
31
|
+
events: []
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const raw = fs_1.default.readFileSync(filePath, "utf-8");
|
|
36
|
+
const parsed = JSON.parse(raw);
|
|
37
|
+
return {
|
|
38
|
+
firstSeenAt: parsed.firstSeenAt || new Date().toISOString(),
|
|
39
|
+
lastSeenAt: parsed.lastSeenAt || new Date().toISOString(),
|
|
40
|
+
commandCounts: parsed.commandCounts || {},
|
|
41
|
+
activation: parsed.activation || { started: 0, completed: 0 },
|
|
42
|
+
events: Array.isArray(parsed.events) ? parsed.events : []
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
const now = new Date().toISOString();
|
|
47
|
+
return {
|
|
48
|
+
firstSeenAt: now,
|
|
49
|
+
lastSeenAt: now,
|
|
50
|
+
commandCounts: {},
|
|
51
|
+
activation: { started: 0, completed: 0 },
|
|
52
|
+
events: []
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function recordCommandMetric(command) {
|
|
57
|
+
if (!isEnabled()) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const file = metricsPath();
|
|
61
|
+
const snapshot = loadSnapshot(file);
|
|
62
|
+
const now = new Date().toISOString();
|
|
63
|
+
snapshot.lastSeenAt = now;
|
|
64
|
+
snapshot.commandCounts[command] = (snapshot.commandCounts[command] || 0) + 1;
|
|
65
|
+
snapshot.events.push({ at: now, type: "command", data: { command } });
|
|
66
|
+
if (snapshot.events.length > 200) {
|
|
67
|
+
snapshot.events = snapshot.events.slice(snapshot.events.length - 200);
|
|
68
|
+
}
|
|
69
|
+
fs_1.default.writeFileSync(file, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
70
|
+
}
|
|
71
|
+
function recordActivationMetric(type, data) {
|
|
72
|
+
if (!isEnabled()) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const file = metricsPath();
|
|
76
|
+
const snapshot = loadSnapshot(file);
|
|
77
|
+
const now = new Date().toISOString();
|
|
78
|
+
snapshot.lastSeenAt = now;
|
|
79
|
+
snapshot.activation[type] = (snapshot.activation[type] || 0) + 1;
|
|
80
|
+
snapshot.events.push({ at: now, type: `activation.${type}`, data });
|
|
81
|
+
if (snapshot.events.length > 200) {
|
|
82
|
+
snapshot.events = snapshot.events.slice(snapshot.events.length - 200);
|
|
83
|
+
}
|
|
84
|
+
fs_1.default.writeFileSync(file, JSON.stringify(snapshot, null, 2), "utf-8");
|
|
85
|
+
}
|
|
@@ -17,7 +17,11 @@ export type ProjectInfo = {
|
|
|
17
17
|
name: string;
|
|
18
18
|
root: string;
|
|
19
19
|
};
|
|
20
|
+
export declare function normalizeScopeName(name: string): string;
|
|
20
21
|
export declare function getWorkspaceInfo(): WorkspaceInfo;
|
|
22
|
+
export declare function getWorkspaceInfoForScope(scope?: string): WorkspaceInfo;
|
|
23
|
+
export declare function getWorkspaceBaseRoot(): string;
|
|
24
|
+
export declare function listScopes(baseRoot?: string): string[];
|
|
21
25
|
export declare function ensureWorkspace(workspace: WorkspaceInfo): void;
|
|
22
26
|
export declare function normalizeProjectName(name: string): string;
|
|
23
27
|
export declare function getProjectInfo(workspace: WorkspaceInfo, name: string): ProjectInfo;
|
package/dist/workspace/index.js
CHANGED
|
@@ -3,7 +3,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.normalizeScopeName = normalizeScopeName;
|
|
6
7
|
exports.getWorkspaceInfo = getWorkspaceInfo;
|
|
8
|
+
exports.getWorkspaceInfoForScope = getWorkspaceInfoForScope;
|
|
9
|
+
exports.getWorkspaceBaseRoot = getWorkspaceBaseRoot;
|
|
10
|
+
exports.listScopes = listScopes;
|
|
7
11
|
exports.ensureWorkspace = ensureWorkspace;
|
|
8
12
|
exports.normalizeProjectName = normalizeProjectName;
|
|
9
13
|
exports.getProjectInfo = getProjectInfo;
|
|
@@ -15,6 +19,22 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
15
19
|
const path_1 = __importDefault(require("path"));
|
|
16
20
|
const os_1 = __importDefault(require("os"));
|
|
17
21
|
const flags_1 = require("../context/flags");
|
|
22
|
+
const WORKSPACE_LOCK_RETRY_MS = 50;
|
|
23
|
+
const WORKSPACE_LOCK_WAIT_MS = 5000;
|
|
24
|
+
const WORKSPACE_LOCK_STALE_MS = 30000;
|
|
25
|
+
function normalizeScopeName(name) {
|
|
26
|
+
const trimmed = name.trim();
|
|
27
|
+
if (!trimmed) {
|
|
28
|
+
throw new Error("Scope name is required.");
|
|
29
|
+
}
|
|
30
|
+
if (trimmed.includes("..") || trimmed.includes("/") || trimmed.includes("\\")) {
|
|
31
|
+
throw new Error("Scope name cannot contain path separators.");
|
|
32
|
+
}
|
|
33
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9 _-]*$/.test(trimmed)) {
|
|
34
|
+
throw new Error("Scope name must use letters, numbers, spaces, '-' or '_' only.");
|
|
35
|
+
}
|
|
36
|
+
return trimmed;
|
|
37
|
+
}
|
|
18
38
|
function readJsonFile(filePath) {
|
|
19
39
|
try {
|
|
20
40
|
const raw = fs_1.default.readFileSync(filePath, "utf-8");
|
|
@@ -31,24 +51,102 @@ function readWorkspaceIndex(workspace) {
|
|
|
31
51
|
}
|
|
32
52
|
return { projects: parsed.projects };
|
|
33
53
|
}
|
|
54
|
+
function sleepSync(ms) {
|
|
55
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
56
|
+
}
|
|
57
|
+
function isStaleLock(lockPath) {
|
|
58
|
+
try {
|
|
59
|
+
const stats = fs_1.default.statSync(lockPath);
|
|
60
|
+
return Date.now() - stats.mtimeMs > WORKSPACE_LOCK_STALE_MS;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function withWorkspaceIndexLock(workspace, fn) {
|
|
67
|
+
const lockPath = `${workspace.indexPath}.lock`;
|
|
68
|
+
const start = Date.now();
|
|
69
|
+
while (true) {
|
|
70
|
+
try {
|
|
71
|
+
const fd = fs_1.default.openSync(lockPath, "wx");
|
|
72
|
+
try {
|
|
73
|
+
const payload = JSON.stringify({ pid: process.pid, createdAt: new Date().toISOString() });
|
|
74
|
+
fs_1.default.writeFileSync(fd, payload, "utf-8");
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
fs_1.default.closeSync(fd);
|
|
78
|
+
}
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
const err = error;
|
|
83
|
+
if (err.code !== "EEXIST") {
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
86
|
+
if (isStaleLock(lockPath)) {
|
|
87
|
+
fs_1.default.rmSync(lockPath, { force: true });
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (Date.now() - start > WORKSPACE_LOCK_WAIT_MS) {
|
|
91
|
+
throw new Error("Workspace index is locked by another process. Retry shortly.");
|
|
92
|
+
}
|
|
93
|
+
sleepSync(WORKSPACE_LOCK_RETRY_MS);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
return fn();
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
try {
|
|
101
|
+
fs_1.default.rmSync(lockPath, { force: true });
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// ignore cleanup errors
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
34
108
|
function getWorkspaceInfo() {
|
|
35
109
|
const flags = (0, flags_1.getFlags)();
|
|
36
|
-
const
|
|
110
|
+
const scope = flags.scope && flags.scope.trim().length > 0 ? normalizeScopeName(flags.scope) : undefined;
|
|
111
|
+
return getWorkspaceInfoForScope(scope);
|
|
112
|
+
}
|
|
113
|
+
function getWorkspaceInfoForScope(scope) {
|
|
114
|
+
const baseRoot = getWorkspaceBaseRoot();
|
|
115
|
+
const normalizedScope = scope && scope.trim().length > 0 ? normalizeScopeName(scope) : "";
|
|
116
|
+
const root = normalizedScope ? path_1.default.join(baseRoot, normalizedScope) : baseRoot;
|
|
117
|
+
const indexPath = path_1.default.join(root, "workspaces.json");
|
|
118
|
+
return { root, indexPath };
|
|
119
|
+
}
|
|
120
|
+
function getWorkspaceBaseRoot() {
|
|
121
|
+
const flags = (0, flags_1.getFlags)();
|
|
122
|
+
return flags.output
|
|
37
123
|
? path_1.default.resolve(flags.output)
|
|
38
124
|
: process.env.APPDATA
|
|
39
125
|
? path_1.default.join(process.env.APPDATA, "sdd-cli", "workspaces")
|
|
40
126
|
: path_1.default.join(os_1.default.homedir(), ".config", "sdd-cli", "workspaces");
|
|
41
|
-
|
|
42
|
-
|
|
127
|
+
}
|
|
128
|
+
function listScopes(baseRoot) {
|
|
129
|
+
const root = baseRoot ? path_1.default.resolve(baseRoot) : getWorkspaceBaseRoot();
|
|
130
|
+
if (!fs_1.default.existsSync(root)) {
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
return fs_1.default
|
|
134
|
+
.readdirSync(root, { withFileTypes: true })
|
|
135
|
+
.filter((entry) => entry.isDirectory())
|
|
136
|
+
.map((entry) => entry.name)
|
|
137
|
+
.filter((name) => fs_1.default.existsSync(path_1.default.join(root, name, "workspaces.json")))
|
|
138
|
+
.sort();
|
|
43
139
|
}
|
|
44
140
|
function ensureWorkspace(workspace) {
|
|
45
141
|
if (!fs_1.default.existsSync(workspace.root)) {
|
|
46
142
|
fs_1.default.mkdirSync(workspace.root, { recursive: true });
|
|
47
143
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
144
|
+
withWorkspaceIndexLock(workspace, () => {
|
|
145
|
+
if (!fs_1.default.existsSync(workspace.indexPath)) {
|
|
146
|
+
const emptyIndex = { projects: [] };
|
|
147
|
+
fs_1.default.writeFileSync(workspace.indexPath, JSON.stringify(emptyIndex, null, 2), "utf-8");
|
|
148
|
+
}
|
|
149
|
+
});
|
|
52
150
|
}
|
|
53
151
|
function normalizeProjectName(name) {
|
|
54
152
|
const trimmed = name.trim();
|
|
@@ -121,16 +219,18 @@ function ensureProject(workspace, name, domain) {
|
|
|
121
219
|
};
|
|
122
220
|
fs_1.default.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2), "utf-8");
|
|
123
221
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
existing
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
222
|
+
withWorkspaceIndexLock(workspace, () => {
|
|
223
|
+
const index = readWorkspaceIndex(workspace);
|
|
224
|
+
index.projects = index.projects ?? [];
|
|
225
|
+
const existing = index.projects.find((entry) => entry.name === project.name);
|
|
226
|
+
if (existing) {
|
|
227
|
+
existing.status = metadata.status;
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
index.projects.push({ name: metadata.name, status: metadata.status });
|
|
231
|
+
}
|
|
232
|
+
fs_1.default.writeFileSync(workspace.indexPath, JSON.stringify(index, null, 2), "utf-8");
|
|
233
|
+
});
|
|
134
234
|
return metadata;
|
|
135
235
|
}
|
|
136
236
|
function createProject(workspace, name, domain) {
|
|
@@ -139,16 +239,18 @@ function createProject(workspace, name, domain) {
|
|
|
139
239
|
function updateProjectStatus(workspace, name, status) {
|
|
140
240
|
ensureWorkspace(workspace);
|
|
141
241
|
const project = getProjectInfo(workspace, name);
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
existing
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
242
|
+
withWorkspaceIndexLock(workspace, () => {
|
|
243
|
+
const index = readWorkspaceIndex(workspace);
|
|
244
|
+
index.projects = index.projects ?? [];
|
|
245
|
+
const existing = index.projects.find((entry) => entry.name === project.name);
|
|
246
|
+
if (existing) {
|
|
247
|
+
existing.status = status;
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
index.projects.push({ name: project.name, status });
|
|
251
|
+
}
|
|
252
|
+
fs_1.default.writeFileSync(workspace.indexPath, JSON.stringify(index, null, 2), "utf-8");
|
|
253
|
+
});
|
|
152
254
|
const projectRoot = project.root;
|
|
153
255
|
const metadataPath = path_1.default.join(projectRoot, "metadata.json");
|
|
154
256
|
const metadata = fs_1.default.existsSync(metadataPath) ? readJsonFile(metadataPath) : null;
|
package/package.json
CHANGED
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sdd-cli",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.21",
|
|
4
|
+
"description": "Use sdd-cli to turn ideas, GitHub/Jira work items, and PR feedback into actionable requirements, specs, plans, and done-ready delivery records with guided workflows.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"cli",
|
|
7
|
+
"specification-driven-development",
|
|
8
|
+
"software-design-document",
|
|
9
|
+
"requirements",
|
|
10
|
+
"architecture",
|
|
11
|
+
"technical-spec",
|
|
12
|
+
"workflow",
|
|
13
|
+
"autopilot",
|
|
14
|
+
"project-management"
|
|
15
|
+
],
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=20"
|
|
19
|
+
},
|
|
5
20
|
"homepage": "https://github.com/jdsalasca/sdd-tool#readme",
|
|
6
21
|
"repository": {
|
|
7
22
|
"type": "git",
|
|
@@ -28,6 +43,13 @@
|
|
|
28
43
|
"start": "node dist/cli.js",
|
|
29
44
|
"dev": "ts-node src/cli.ts",
|
|
30
45
|
"check:docs": "node scripts/check-docs-flags.js",
|
|
46
|
+
"check:error-codes": "node scripts/check-error-codes.js",
|
|
47
|
+
"release:notes": "node scripts/generate-release-notes.js",
|
|
48
|
+
"release:changelog": "node scripts/update-changelog.js",
|
|
49
|
+
"release:metrics": "node scripts/post-release-metrics.js",
|
|
50
|
+
"verify:publish": "node scripts/verify-publish-ready.js",
|
|
51
|
+
"verify:release-tag": "node scripts/verify-release-version.js",
|
|
52
|
+
"metrics:summary": "node scripts/metrics-summary.js",
|
|
31
53
|
"smoke:autopilot": "node scripts/autopilot-smoke.js",
|
|
32
54
|
"preinstall": "node scripts/preinstall.js",
|
|
33
55
|
"pretest": "npm run build",
|