@topogram/cli 0.3.71 → 0.3.73
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 +24 -195
- package/package.json +1 -1
- package/src/adoption/plan/index.js +2 -1
- package/src/agent-brief.js +46 -2
- package/src/archive/archive.js +1 -1
- package/src/archive/jsonl.js +18 -8
- package/src/archive/resolver-bridge.js +34 -1
- package/src/archive/schema.js +1 -1
- package/src/archive/unarchive.js +26 -0
- package/src/cli/command-parsers/project.js +0 -3
- package/src/cli/command-parsers/sdlc.js +66 -0
- package/src/cli/commands/import/help.js +1 -0
- package/src/cli/commands/import/plan.js +9 -0
- package/src/cli/commands/import/workspace.js +3 -0
- package/src/cli/commands/query/definitions.js +11 -10
- package/src/cli/commands/query/workspace.js +23 -2
- package/src/cli/commands/sdlc.js +213 -5
- package/src/cli/dispatcher.js +8 -5
- package/src/cli/help.js +14 -3
- package/src/cli/migration-guidance.js +3 -0
- package/src/cli/options.js +1 -0
- package/src/generator/context/shared/domain-sdlc.js +27 -0
- package/src/generator/context/shared/relationships.js +2 -1
- package/src/generator/context/shared/types.d.ts +1 -0
- package/src/generator/context/shared.d.ts +2 -0
- package/src/generator/context/shared.js +2 -0
- package/src/generator/context/slice/core.js +3 -0
- package/src/generator/context/slice/sdlc.js +57 -2
- package/src/generator/context/task-mode.js +7 -0
- package/src/generator/sdlc/board.js +2 -0
- package/src/generator/sdlc/traceability-matrix.js +5 -1
- package/src/generator/surfaces/databases/lifecycle-shared.js +2 -2
- package/src/import/core/context.js +1 -1
- package/src/import/core/contracts.js +3 -3
- package/src/import/core/registry.js +3 -0
- package/src/import/core/runner/candidates.js +7 -0
- package/src/import/core/runner/reports.js +9 -1
- package/src/import/core/runner/tracks.js +3 -0
- package/src/import/extractors/cli/generic.js +340 -0
- package/src/new-project/project-files.js +10 -3
- package/src/resolver/enrich/task.js +3 -1
- package/src/resolver/index.js +6 -0
- package/src/resolver/normalize.js +31 -0
- package/src/resolver/projections-cli.js +158 -0
- package/src/sdlc/adopt.js +4 -1
- package/src/sdlc/check.js +24 -2
- package/src/sdlc/complete.js +47 -0
- package/src/sdlc/dod/index.js +2 -0
- package/src/sdlc/dod/plan.js +15 -0
- package/src/sdlc/dod/task.js +7 -3
- package/src/sdlc/explain.js +53 -1
- package/src/sdlc/gate.js +352 -0
- package/src/sdlc/history.d.ts +7 -0
- package/src/sdlc/history.js +50 -5
- package/src/sdlc/link.js +172 -0
- package/src/sdlc/paths.d.ts +4 -0
- package/src/sdlc/paths.js +8 -0
- package/src/sdlc/plan-steps.js +71 -0
- package/src/sdlc/plan.js +245 -0
- package/src/sdlc/policy.js +249 -0
- package/src/sdlc/prep.js +186 -0
- package/src/sdlc/scaffold.js +4 -2
- package/src/sdlc/status-filter.js +2 -0
- package/src/sdlc/transitions/index.js +3 -0
- package/src/sdlc/transitions/plan.js +32 -0
- package/src/validator/common.js +25 -4
- package/src/validator/index.js +10 -0
- package/src/validator/kinds.d.ts +7 -0
- package/src/validator/kinds.js +32 -0
- package/src/validator/per-kind/plan.js +128 -0
- package/src/validator/per-kind/task.js +19 -0
- package/src/validator/projections/cli.js +267 -0
- package/src/validator.d.ts +1 -0
- package/src/workflows/import-app/shared.js +1 -1
- package/src/workflows/reconcile/adoption-plan/build.js +3 -1
- package/src/workflows/reconcile/adoption-plan/reasons.js +5 -0
- package/src/workflows/reconcile/bundle-core/index.js +3 -0
- package/src/workflows/reconcile/candidate-model.js +15 -0
- package/src/workflows/reconcile/gap-report.js +4 -2
- package/src/workflows/reconcile/impacts/adoption-plan.js +13 -0
- package/src/workflows/reconcile/renderers.js +82 -0
- package/src/workflows/reconcile/summary.js +4 -0
- package/src/workflows/reconcile/workflow.js +2 -1
- package/src/workspace-paths.js +34 -3
- package/src/cli/commands/migrate.js +0 -153
package/src/workspace-paths.js
CHANGED
|
@@ -4,10 +4,10 @@ import fs from "node:fs";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
|
|
6
6
|
export const DEFAULT_TOPO_FOLDER_NAME = "topo";
|
|
7
|
-
export const LEGACY_TOPOGRAM_FOLDER_NAME = "topogram";
|
|
8
7
|
export const DEFAULT_WORKSPACE_PATH = `./${DEFAULT_TOPO_FOLDER_NAME}`;
|
|
9
8
|
export const PROJECT_CONFIG_FILE = "topogram.project.json";
|
|
10
9
|
|
|
10
|
+
const LEGACY_WORKSPACE_FOLDER_NAME = "topogram";
|
|
11
11
|
const SIGNAL_SCAN_IGNORED_DIRS = new Set([
|
|
12
12
|
".git",
|
|
13
13
|
".next",
|
|
@@ -19,6 +19,7 @@ const SIGNAL_SCAN_IGNORED_DIRS = new Set([
|
|
|
19
19
|
"coverage",
|
|
20
20
|
"dist",
|
|
21
21
|
"expected",
|
|
22
|
+
LEGACY_WORKSPACE_FOLDER_NAME,
|
|
22
23
|
"node_modules",
|
|
23
24
|
"tmp"
|
|
24
25
|
]);
|
|
@@ -56,6 +57,11 @@ const WORKSPACE_SIGNAL_DIRS = new Set([
|
|
|
56
57
|
* @property {boolean} bootstrappedTopoRoot
|
|
57
58
|
*/
|
|
58
59
|
|
|
60
|
+
/**
|
|
61
|
+
* @typedef {Object} WorkspaceResolutionOptions
|
|
62
|
+
* @property {boolean} [ignoreAncestorConfig]
|
|
63
|
+
*/
|
|
64
|
+
|
|
59
65
|
/**
|
|
60
66
|
* @param {string} candidatePath
|
|
61
67
|
* @returns {boolean}
|
|
@@ -128,6 +134,9 @@ export function normalizeWorkspaceConfigPath(workspacePath) {
|
|
|
128
134
|
if (resolved === ".." || resolved.startsWith("../")) {
|
|
129
135
|
throw new Error("topogram.project.json workspace must not escape the project root.");
|
|
130
136
|
}
|
|
137
|
+
if (resolved === LEGACY_WORKSPACE_FOLDER_NAME || resolved.startsWith(`${LEGACY_WORKSPACE_FOLDER_NAME}/`)) {
|
|
138
|
+
throw new Error("topogram.project.json workspace must use ./topo or another non-legacy relative path.");
|
|
139
|
+
}
|
|
131
140
|
return normalized;
|
|
132
141
|
}
|
|
133
142
|
|
|
@@ -221,10 +230,14 @@ function signalWorkspaceCandidates(root) {
|
|
|
221
230
|
|
|
222
231
|
/**
|
|
223
232
|
* @param {string} inputPath
|
|
233
|
+
* @param {WorkspaceResolutionOptions} [options]
|
|
224
234
|
* @returns {WorkspaceResolution}
|
|
225
235
|
*/
|
|
226
|
-
export function resolveWorkspaceContext(inputPath = ".") {
|
|
236
|
+
export function resolveWorkspaceContext(inputPath = ".", options = {}) {
|
|
227
237
|
const absolute = path.resolve(inputPath || ".");
|
|
238
|
+
if (isDirectory(absolute) && path.basename(absolute) === LEGACY_WORKSPACE_FOLDER_NAME && isWorkspaceSignalRoot(absolute)) {
|
|
239
|
+
throw new Error("Legacy workspace folders are not supported. Use topo/ or configure topogram.project.json workspace to a non-legacy relative path.");
|
|
240
|
+
}
|
|
228
241
|
if (
|
|
229
242
|
isDirectory(absolute) &&
|
|
230
243
|
(
|
|
@@ -243,7 +256,25 @@ export function resolveWorkspaceContext(inputPath = ".") {
|
|
|
243
256
|
};
|
|
244
257
|
}
|
|
245
258
|
|
|
246
|
-
const
|
|
259
|
+
const directDefaultCandidate = path.join(absolute, DEFAULT_TOPO_FOLDER_NAME);
|
|
260
|
+
if (
|
|
261
|
+
isDirectory(absolute) &&
|
|
262
|
+
!fs.existsSync(path.join(absolute, PROJECT_CONFIG_FILE)) &&
|
|
263
|
+
isDirectory(directDefaultCandidate) &&
|
|
264
|
+
isWorkspaceSignalRoot(directDefaultCandidate)
|
|
265
|
+
) {
|
|
266
|
+
return {
|
|
267
|
+
inputRoot: absolute,
|
|
268
|
+
topoRoot: directDefaultCandidate,
|
|
269
|
+
projectRoot: absolute,
|
|
270
|
+
configPath: null,
|
|
271
|
+
fromConfig: false,
|
|
272
|
+
fromSignal: false,
|
|
273
|
+
bootstrappedTopoRoot: false
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const configInfo = options.ignoreAncestorConfig ? null : findProjectRoot(absolute);
|
|
247
278
|
if (configInfo) {
|
|
248
279
|
const topoRoot = resolveProjectWorkspace(configInfo.config, configInfo.configDir);
|
|
249
280
|
return {
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
|
|
6
|
-
import { stableStringify } from "../../format.js";
|
|
7
|
-
import {
|
|
8
|
-
DEFAULT_TOPO_FOLDER_NAME,
|
|
9
|
-
DEFAULT_WORKSPACE_PATH,
|
|
10
|
-
LEGACY_TOPOGRAM_FOLDER_NAME,
|
|
11
|
-
PROJECT_CONFIG_FILE
|
|
12
|
-
} from "../../workspace-paths.js";
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* @param {string|null|undefined} inputPath
|
|
16
|
-
* @returns {string}
|
|
17
|
-
*/
|
|
18
|
-
function projectRootForMigration(inputPath) {
|
|
19
|
-
const absolute = path.resolve(inputPath || ".");
|
|
20
|
-
const base = path.basename(absolute);
|
|
21
|
-
if (base === DEFAULT_TOPO_FOLDER_NAME || base === LEGACY_TOPOGRAM_FOLDER_NAME) {
|
|
22
|
-
return path.dirname(absolute);
|
|
23
|
-
}
|
|
24
|
-
return absolute;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* @param {string} projectRoot
|
|
29
|
-
* @returns {string[]}
|
|
30
|
-
*/
|
|
31
|
-
function caseCollisionEntries(projectRoot) {
|
|
32
|
-
if (!fs.existsSync(projectRoot) || !fs.statSync(projectRoot).isDirectory()) {
|
|
33
|
-
return [];
|
|
34
|
-
}
|
|
35
|
-
return fs.readdirSync(projectRoot)
|
|
36
|
-
.filter((/** @type {string} */ entry) => entry.toLowerCase() === DEFAULT_TOPO_FOLDER_NAME && entry !== DEFAULT_TOPO_FOLDER_NAME);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* @param {string} projectRoot
|
|
41
|
-
* @returns {{ write: boolean, path: string|null, before: any|null, after: any|null }}
|
|
42
|
-
*/
|
|
43
|
-
function plannedProjectConfigUpdate(projectRoot) {
|
|
44
|
-
const configPath = path.join(projectRoot, PROJECT_CONFIG_FILE);
|
|
45
|
-
if (!fs.existsSync(configPath)) {
|
|
46
|
-
return { write: false, path: null, before: null, after: null };
|
|
47
|
-
}
|
|
48
|
-
const before = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
49
|
-
const after = { ...before };
|
|
50
|
-
const currentWorkspace = before.workspace;
|
|
51
|
-
if (currentWorkspace == null || currentWorkspace === "./topogram" || currentWorkspace === "topogram") {
|
|
52
|
-
after.workspace = DEFAULT_WORKSPACE_PATH;
|
|
53
|
-
}
|
|
54
|
-
return {
|
|
55
|
-
write: JSON.stringify(before) !== JSON.stringify(after),
|
|
56
|
-
path: configPath,
|
|
57
|
-
before,
|
|
58
|
-
after
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* @param {string|null|undefined} inputPath
|
|
64
|
-
* @param {{ write?: boolean, json?: boolean }} [options]
|
|
65
|
-
* @returns {number}
|
|
66
|
-
*/
|
|
67
|
-
export function runMigrateCommand(inputPath, options = {}) {
|
|
68
|
-
const projectRoot = projectRootForMigration(inputPath);
|
|
69
|
-
const legacyPath = path.join(projectRoot, LEGACY_TOPOGRAM_FOLDER_NAME);
|
|
70
|
-
const topoPath = path.join(projectRoot, DEFAULT_TOPO_FOLDER_NAME);
|
|
71
|
-
const write = Boolean(options.write);
|
|
72
|
-
/** @type {Array<Record<string, any>>} */
|
|
73
|
-
const diagnostics = [];
|
|
74
|
-
/** @type {Array<Record<string, any>>} */
|
|
75
|
-
const actions = [];
|
|
76
|
-
|
|
77
|
-
if (fs.existsSync(legacyPath) && fs.lstatSync(legacyPath).isSymbolicLink()) {
|
|
78
|
-
diagnostics.push({ severity: "error", message: `Refusing to migrate symlinked ${LEGACY_TOPOGRAM_FOLDER_NAME}/ at ${legacyPath}.` });
|
|
79
|
-
}
|
|
80
|
-
const collisions = caseCollisionEntries(projectRoot);
|
|
81
|
-
if (collisions.length > 0) {
|
|
82
|
-
diagnostics.push({ severity: "error", message: `Refusing to migrate because case-conflicting topo path(s) exist: ${collisions.join(", ")}.` });
|
|
83
|
-
}
|
|
84
|
-
if (fs.existsSync(legacyPath) && fs.existsSync(topoPath)) {
|
|
85
|
-
diagnostics.push({ severity: "error", message: `Refusing to migrate because both ${LEGACY_TOPOGRAM_FOLDER_NAME}/ and ${DEFAULT_TOPO_FOLDER_NAME}/ exist.` });
|
|
86
|
-
}
|
|
87
|
-
if (!fs.existsSync(legacyPath) && !fs.existsSync(topoPath)) {
|
|
88
|
-
diagnostics.push({ severity: "error", message: `No ${LEGACY_TOPOGRAM_FOLDER_NAME}/ or ${DEFAULT_TOPO_FOLDER_NAME}/ workspace folder found at ${projectRoot}.` });
|
|
89
|
-
}
|
|
90
|
-
if (fs.existsSync(topoPath) && fs.statSync(topoPath).isDirectory() && fs.readdirSync(topoPath).length > 0 && fs.existsSync(legacyPath)) {
|
|
91
|
-
diagnostics.push({ severity: "error", message: `Refusing to overwrite non-empty ${DEFAULT_TOPO_FOLDER_NAME}/ at ${topoPath}.` });
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (fs.existsSync(legacyPath) && diagnostics.length === 0) {
|
|
95
|
-
actions.push({
|
|
96
|
-
kind: "rename",
|
|
97
|
-
from: legacyPath,
|
|
98
|
-
to: topoPath
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
const configUpdate = plannedProjectConfigUpdate(projectRoot);
|
|
102
|
-
if (configUpdate.write) {
|
|
103
|
-
actions.push({
|
|
104
|
-
kind: "update_config",
|
|
105
|
-
path: configUpdate.path,
|
|
106
|
-
workspace: DEFAULT_WORKSPACE_PATH
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const ok = diagnostics.filter((diagnostic) => diagnostic.severity === "error").length === 0;
|
|
111
|
-
if (ok && write) {
|
|
112
|
-
for (const action of actions) {
|
|
113
|
-
if (action.kind === "rename") {
|
|
114
|
-
fs.renameSync(action.from, action.to);
|
|
115
|
-
}
|
|
116
|
-
if (action.kind === "update_config" && configUpdate.path && configUpdate.after) {
|
|
117
|
-
fs.writeFileSync(configUpdate.path, `${JSON.stringify(configUpdate.after, null, 2)}\n`, "utf8");
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const payload = {
|
|
123
|
-
ok,
|
|
124
|
-
dryRun: !write,
|
|
125
|
-
projectRoot,
|
|
126
|
-
legacyPath,
|
|
127
|
-
topoPath,
|
|
128
|
-
actions,
|
|
129
|
-
diagnostics,
|
|
130
|
-
errors: diagnostics.filter((diagnostic) => diagnostic.severity === "error").map((diagnostic) => diagnostic.message)
|
|
131
|
-
};
|
|
132
|
-
if (options.json) {
|
|
133
|
-
console.log(stableStringify(payload));
|
|
134
|
-
} else if (payload.ok) {
|
|
135
|
-
console.log(write ? "Workspace folder migration complete." : "Workspace folder migration dry run.");
|
|
136
|
-
if (actions.length === 0) {
|
|
137
|
-
console.log("No changes needed.");
|
|
138
|
-
}
|
|
139
|
-
for (const action of actions) {
|
|
140
|
-
if (action.kind === "rename") {
|
|
141
|
-
console.log(`Rename: ${action.from} -> ${action.to}`);
|
|
142
|
-
}
|
|
143
|
-
if (action.kind === "update_config") {
|
|
144
|
-
console.log(`Update ${action.path}: workspace ${DEFAULT_WORKSPACE_PATH}`);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
} else {
|
|
148
|
-
for (const error of payload.errors) {
|
|
149
|
-
console.error(error);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
return payload.ok ? 0 : 1;
|
|
153
|
-
}
|