create-sdd-project 0.16.10 → 0.17.1
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/lib/adapt-agents.js +121 -29
- package/lib/diff-generator.js +7 -1
- package/lib/doctor.js +166 -0
- package/lib/generator.js +8 -0
- package/lib/init-generator.js +67 -160
- package/lib/meta.js +344 -0
- package/lib/scanner.js +127 -6
- package/lib/stack-adaptations.js +335 -0
- package/lib/upgrade-generator.js +552 -157
- package/package.json +1 -1
- package/template/gitignore +3 -0
package/lib/adapt-agents.js
CHANGED
|
@@ -65,6 +65,99 @@ const AGENT_ADAPTATION_RULES = {
|
|
|
65
65
|
},
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* v0.17.1: project-type-specific pruning rules for workflow-core files
|
|
70
|
+
* (SKILL.md + ticket-template.md). Keys are POSIX suffixes relative to
|
|
71
|
+
* the tool dir (e.g. `skills/development-workflow/SKILL.md`) so the same
|
|
72
|
+
* rules apply to both `.claude/` and `.gemini/` trees.
|
|
73
|
+
*
|
|
74
|
+
* These rules are the pure/in-memory equivalent of the inline block in
|
|
75
|
+
* `adaptAgentContentForProjectType`. Exposed here so upgrade-generator.js
|
|
76
|
+
* can build an accurate "what init would have produced" comparison target
|
|
77
|
+
* for smart-diff fallback paths — previously this was masked by
|
|
78
|
+
* unconditional `filesToAdapt.add` calls (pre-v0.17.1) that re-applied
|
|
79
|
+
* stack rules to restored user content, violating Codex M1 (Gemini round-3
|
|
80
|
+
* finding 1). Source of truth now lives here; disk-writing code below
|
|
81
|
+
* calls these same tables.
|
|
82
|
+
*/
|
|
83
|
+
const WORKFLOW_CORE_PROJECT_TYPE_RULES = {
|
|
84
|
+
backend: {
|
|
85
|
+
'skills/development-workflow/SKILL.md': [
|
|
86
|
+
[/,? `ui-components\.md`\)/, ')'],
|
|
87
|
+
[/- UI components → `docs\/specs\/ui-components\.md` \(MANDATORY\)\n/, ''],
|
|
88
|
+
[/\d+\. \*\*Design Review \(optional\):\*\*[^\n]*\n/, ''],
|
|
89
|
+
],
|
|
90
|
+
'skills/development-workflow/references/ticket-template.md': [
|
|
91
|
+
[/### UI Changes \(if applicable\)\n\n\[Components to add\/modify\. Reference `docs\/specs\/ui-components\.md`\.\]\n\n/, ''],
|
|
92
|
+
[' / `ui-components.md`', ''],
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
frontend: {
|
|
96
|
+
'skills/development-workflow/SKILL.md': [
|
|
97
|
+
[/`api-spec\.yaml`,? /, ''],
|
|
98
|
+
[/- API endpoints → `docs\/specs\/api-spec\.yaml` \(MANDATORY\)\n/, ''],
|
|
99
|
+
],
|
|
100
|
+
'skills/development-workflow/references/ticket-template.md': [
|
|
101
|
+
[/### API Changes \(if applicable\)\n\n\[Endpoints to add\/modify\. Reference[^\]]*\]\n\n/, ''],
|
|
102
|
+
['`api-spec.yaml` / ', ''],
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const BASE_STANDARDS_PROJECT_TYPE_RULES = {
|
|
108
|
+
backend: [
|
|
109
|
+
[/\| `ui-ux-designer` \|[^\n]*\n/, ''],
|
|
110
|
+
],
|
|
111
|
+
// frontend: no extra rules (base-standards template has no frontend-only refs to strip)
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
function applyProjectTypeRules(content, rules) {
|
|
115
|
+
let result = content;
|
|
116
|
+
for (const [search, replace] of rules) {
|
|
117
|
+
if (search instanceof RegExp) {
|
|
118
|
+
result = result.replace(search, replace);
|
|
119
|
+
} else {
|
|
120
|
+
result = result.split(search).join(replace);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* v0.17.1 pure helper — apply project-type rules to a workflow-core file
|
|
128
|
+
* content. Returns content unchanged if projectType is fullstack or if
|
|
129
|
+
* posixPath doesn't match a known workflow-core file.
|
|
130
|
+
*
|
|
131
|
+
* @param {string} content - Raw or stack-adapted content
|
|
132
|
+
* @param {string} posixPath - Full POSIX path (e.g. '.claude/skills/development-workflow/SKILL.md')
|
|
133
|
+
* @param {string} projectType - 'fullstack' | 'backend' | 'frontend'
|
|
134
|
+
* @returns {string}
|
|
135
|
+
*/
|
|
136
|
+
function adaptWorkflowCoreContentForProjectType(content, posixPath, projectType) {
|
|
137
|
+
if (projectType === 'fullstack') return content;
|
|
138
|
+
const rulesForType = WORKFLOW_CORE_PROJECT_TYPE_RULES[projectType];
|
|
139
|
+
if (!rulesForType) return content;
|
|
140
|
+
|
|
141
|
+
// Strip the tool prefix (.claude/ or .gemini/) to match the rule key.
|
|
142
|
+
const match = posixPath.match(/^\.(?:claude|gemini)\/(.+)$/);
|
|
143
|
+
if (!match) return content;
|
|
144
|
+
const rules = rulesForType[match[1]];
|
|
145
|
+
if (!rules) return content;
|
|
146
|
+
|
|
147
|
+
return applyProjectTypeRules(content, rules);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* v0.17.1 pure helper — apply project-type rules to base-standards.mdc
|
|
152
|
+
* content. Called AFTER `adaptBaseStandards` to produce the full init
|
|
153
|
+
* equivalent for smart-diff comparison.
|
|
154
|
+
*/
|
|
155
|
+
function adaptBaseStandardsContentForProjectType(content, projectType) {
|
|
156
|
+
const rules = BASE_STANDARDS_PROJECT_TYPE_RULES[projectType];
|
|
157
|
+
if (!rules) return content;
|
|
158
|
+
return applyProjectTypeRules(content, rules);
|
|
159
|
+
}
|
|
160
|
+
|
|
68
161
|
/**
|
|
69
162
|
* Pure function — apply single-stack adaptation rules to an agent file's content.
|
|
70
163
|
*
|
|
@@ -130,43 +223,38 @@ function adaptAgentContentForProjectType(dest, config, replaceInFileFn) {
|
|
|
130
223
|
}
|
|
131
224
|
|
|
132
225
|
// --- Skills and templates: remove frontend/backend-specific references ---
|
|
133
|
-
//
|
|
226
|
+
// v0.17.1: SKILL.md + ticket-template.md rules now come from the
|
|
227
|
+
// WORKFLOW_CORE_PROJECT_TYPE_RULES table above so upgrade-generator.js
|
|
228
|
+
// can apply the same rules in-memory (smart-diff fallback comparison).
|
|
229
|
+
// pr-template.md + AGENTS.md + base-standards.mdc remain inline because
|
|
230
|
+
// they're not workflow-core files (pr-template is v0.17.2 scope).
|
|
231
|
+
const wfRules = WORKFLOW_CORE_PROJECT_TYPE_RULES[config.projectType];
|
|
232
|
+
if (wfRules) {
|
|
233
|
+
for (const dir of toolDirs) {
|
|
234
|
+
for (const [suffix, rules] of Object.entries(wfRules)) {
|
|
235
|
+
replaceInFileFn(path.join(dest, dir, ...suffix.split('/')), rules);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
134
240
|
if (config.projectType === 'backend') {
|
|
241
|
+
// AGENTS.md: remove ui-ux-designer from hook description
|
|
242
|
+
replaceInFileFn(path.join(dest, 'AGENTS.md'), [
|
|
243
|
+
[', `ui-ux-designer`', ''],
|
|
244
|
+
]);
|
|
135
245
|
for (const dir of toolDirs) {
|
|
136
|
-
//
|
|
137
|
-
replaceInFileFn(path.join(dest, dir, 'skills', 'development-workflow', 'SKILL.md'), [
|
|
138
|
-
[/,? `ui-components\.md`\)/, ')'],
|
|
139
|
-
[/- UI components → `docs\/specs\/ui-components\.md` \(MANDATORY\)\n/, ''],
|
|
140
|
-
[/\d+\. \*\*Design Review \(optional\):\*\*[^\n]*\n/, ''],
|
|
141
|
-
]);
|
|
142
|
-
// AGENTS.md: remove ui-ux-designer from hook description
|
|
143
|
-
replaceInFileFn(path.join(dest, 'AGENTS.md'), [
|
|
144
|
-
[', `ui-ux-designer`', ''],
|
|
145
|
-
]);
|
|
146
|
-
// ticket-template: remove UI Changes section, ui-components from checklists
|
|
147
|
-
replaceInFileFn(path.join(dest, dir, 'skills', 'development-workflow', 'references', 'ticket-template.md'), [
|
|
148
|
-
[/### UI Changes \(if applicable\)\n\n\[Components to add\/modify\. Reference `docs\/specs\/ui-components\.md`\.\]\n\n/, ''],
|
|
149
|
-
[' / `ui-components.md`', ''],
|
|
150
|
-
]);
|
|
151
|
-
// pr-template: remove ui-components from checklist
|
|
246
|
+
// pr-template: remove ui-components from checklist (v0.17.2 scope)
|
|
152
247
|
replaceInFileFn(path.join(dest, dir, 'skills', 'development-workflow', 'references', 'pr-template.md'), [
|
|
153
248
|
[' / ui-components.md', ''],
|
|
154
249
|
]);
|
|
155
250
|
}
|
|
156
|
-
//
|
|
157
|
-
replaceInFileFn(
|
|
158
|
-
|
|
159
|
-
|
|
251
|
+
// base-standards.mdc: remove ui-ux-designer table row (shared table above)
|
|
252
|
+
replaceInFileFn(
|
|
253
|
+
path.join(dest, 'ai-specs', 'specs', 'base-standards.mdc'),
|
|
254
|
+
BASE_STANDARDS_PROJECT_TYPE_RULES.backend
|
|
255
|
+
);
|
|
160
256
|
} else if (config.projectType === 'frontend') {
|
|
161
257
|
for (const dir of toolDirs) {
|
|
162
|
-
replaceInFileFn(path.join(dest, dir, 'skills', 'development-workflow', 'SKILL.md'), [
|
|
163
|
-
[/`api-spec\.yaml`,? /, ''],
|
|
164
|
-
[/- API endpoints → `docs\/specs\/api-spec\.yaml` \(MANDATORY\)\n/, ''],
|
|
165
|
-
]);
|
|
166
|
-
replaceInFileFn(path.join(dest, dir, 'skills', 'development-workflow', 'references', 'ticket-template.md'), [
|
|
167
|
-
[/### API Changes \(if applicable\)\n\n\[Endpoints to add\/modify\. Reference[^\]]*\]\n\n/, ''],
|
|
168
|
-
['`api-spec.yaml` / ', ''],
|
|
169
|
-
]);
|
|
170
258
|
replaceInFileFn(path.join(dest, dir, 'skills', 'development-workflow', 'references', 'pr-template.md'), [
|
|
171
259
|
['api-spec.yaml / ', ''],
|
|
172
260
|
]);
|
|
@@ -177,5 +265,9 @@ function adaptAgentContentForProjectType(dest, config, replaceInFileFn) {
|
|
|
177
265
|
module.exports = {
|
|
178
266
|
adaptAgentContentForProjectType,
|
|
179
267
|
adaptAgentContentString,
|
|
268
|
+
adaptWorkflowCoreContentForProjectType,
|
|
269
|
+
adaptBaseStandardsContentForProjectType,
|
|
180
270
|
AGENT_ADAPTATION_RULES,
|
|
271
|
+
WORKFLOW_CORE_PROJECT_TYPE_RULES,
|
|
272
|
+
BASE_STANDARDS_PROJECT_TYPE_RULES,
|
|
181
273
|
};
|
package/lib/diff-generator.js
CHANGED
|
@@ -13,11 +13,17 @@ const {
|
|
|
13
13
|
adaptFrontendStandards,
|
|
14
14
|
} = require('./init-generator');
|
|
15
15
|
const {
|
|
16
|
-
isStandardModified,
|
|
17
16
|
getPackageVersion,
|
|
18
17
|
} = require('./upgrade-generator');
|
|
18
|
+
const { normalizedContentEquals } = require('./meta');
|
|
19
19
|
const { formatScanSummary } = require('./init-wizard');
|
|
20
20
|
|
|
21
|
+
// v0.17.1: isStandardModified was removed. Replace callers with inverted
|
|
22
|
+
// normalizedContentEquals — "modified" means "not equal after normalization".
|
|
23
|
+
function isStandardModified(existing, fresh) {
|
|
24
|
+
return !normalizedContentEquals(existing, fresh);
|
|
25
|
+
}
|
|
26
|
+
|
|
21
27
|
const templateDir = path.join(__dirname, '..', 'template');
|
|
22
28
|
|
|
23
29
|
/**
|
package/lib/doctor.js
CHANGED
|
@@ -79,6 +79,9 @@ function runDoctor(cwd) {
|
|
|
79
79
|
// 14. AGENTS.md Standards References (v0.16.10)
|
|
80
80
|
results.push(checkAgentsMdStandardsRefs(cwd));
|
|
81
81
|
|
|
82
|
+
// 15. .sdd-meta.json structural integrity (v0.17.0)
|
|
83
|
+
results.push(checkMetaJson(cwd, aiTools, projectType));
|
|
84
|
+
|
|
82
85
|
return results;
|
|
83
86
|
}
|
|
84
87
|
|
|
@@ -976,6 +979,24 @@ function checkAgentsMdStandardsRefs(cwd) {
|
|
|
976
979
|
);
|
|
977
980
|
}
|
|
978
981
|
|
|
982
|
+
// v0.17.1 observability (Gemini Q10): warn on sparse Backend/Frontend patterns
|
|
983
|
+
// — exactly 1 entry — suggesting scanner detection missed framework or ORM.
|
|
984
|
+
// Permissive: non-failing, informational. Two+ entries are assumed OK because
|
|
985
|
+
// projects legitimately vary (ORM-only backends, component-less frontends).
|
|
986
|
+
const sparseRe = /(Backend|Frontend) patterns \(([^)]+)\)/g;
|
|
987
|
+
let sparseMatch;
|
|
988
|
+
while ((sparseMatch = sparseRe.exec(content)) !== null) {
|
|
989
|
+
const rawEntries = sparseMatch[2]
|
|
990
|
+
.split(',')
|
|
991
|
+
.map((s) => s.trim())
|
|
992
|
+
.filter((s) => s.length > 0);
|
|
993
|
+
if (rawEntries.length === 1) {
|
|
994
|
+
issues.push(
|
|
995
|
+
`${sparseMatch[1]} patterns has only 1 entry (${rawEntries[0]}) — scanner detection may be incomplete; run \`npx create-sdd-project --upgrade\` after installing your stack deps to re-detect`
|
|
996
|
+
);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
|
|
979
1000
|
// Detect unsubstituted placeholders that look like "[Framework, runtime, version]".
|
|
980
1001
|
// Template placeholders are distinctive: (a) they contain at least one
|
|
981
1002
|
// comma-separated descriptor or the literal word "your", (b) they are NOT
|
|
@@ -1013,6 +1034,151 @@ function checkAgentsMdStandardsRefs(cwd) {
|
|
|
1013
1034
|
};
|
|
1014
1035
|
}
|
|
1015
1036
|
|
|
1037
|
+
/**
|
|
1038
|
+
* Check #15 (v0.17.0): .sdd-meta.json structural integrity.
|
|
1039
|
+
*
|
|
1040
|
+
* v0.17.0 introduces content-addressable hashing via .sdd-meta.json to
|
|
1041
|
+
* track "the last time the tool wrote this file". The upgrade path uses
|
|
1042
|
+
* the hashes to answer "did the user edit since last tool-write" without
|
|
1043
|
+
* comparing against the new template's adapted output (which would drift
|
|
1044
|
+
* across versions — the Codex P1 from v0.16.10 cross-model review).
|
|
1045
|
+
*
|
|
1046
|
+
* This doctor check validates the METADATA STRUCTURE ONLY:
|
|
1047
|
+
* - Valid JSON
|
|
1048
|
+
* - schemaVersion ≤ current
|
|
1049
|
+
* - hashes is a sensible object shape
|
|
1050
|
+
* - Every hash value matches sha256:<64 hex>
|
|
1051
|
+
* - Every key that's NOT in the expected set is flagged as orphan
|
|
1052
|
+
*
|
|
1053
|
+
* It does NOT validate hashes against current on-disk content (Codex M3
|
|
1054
|
+
* from plan v1.0 review). Hash mismatches are the EXPECTED result of
|
|
1055
|
+
* legitimate user customization; reporting them here would generate
|
|
1056
|
+
* permanent noise and bury real integrity issues.
|
|
1057
|
+
*
|
|
1058
|
+
* Severity:
|
|
1059
|
+
* - File absent → PASS with informational message (pre-v0.17.0 project)
|
|
1060
|
+
* - Present and valid → PASS
|
|
1061
|
+
* - Parse/shape errors → WARN (not FAIL — upgrade still falls back safely)
|
|
1062
|
+
* - Orphan entries → WARN (non-fatal, upgrade prunes them next run)
|
|
1063
|
+
*/
|
|
1064
|
+
function checkMetaJson(cwd, aiTools, projectType) {
|
|
1065
|
+
const metaPath = path.join(cwd, '.sdd-meta.json');
|
|
1066
|
+
if (!fs.existsSync(metaPath)) {
|
|
1067
|
+
return {
|
|
1068
|
+
status: PASS,
|
|
1069
|
+
message: 'Provenance metadata: not present (pre-v0.17.0 project or fresh install)',
|
|
1070
|
+
details: [],
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
let raw;
|
|
1075
|
+
try {
|
|
1076
|
+
raw = fs.readFileSync(metaPath, 'utf8');
|
|
1077
|
+
} catch (e) {
|
|
1078
|
+
return {
|
|
1079
|
+
status: WARN,
|
|
1080
|
+
message: '.sdd-meta.json: unreadable',
|
|
1081
|
+
details: [e.code || e.message],
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
let parsed;
|
|
1086
|
+
try {
|
|
1087
|
+
parsed = JSON.parse(raw);
|
|
1088
|
+
} catch (e) {
|
|
1089
|
+
return {
|
|
1090
|
+
status: WARN,
|
|
1091
|
+
message: '.sdd-meta.json: invalid JSON',
|
|
1092
|
+
details: [e.message, 'Next upgrade will regenerate it via the fallback path.'],
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
1097
|
+
return {
|
|
1098
|
+
status: WARN,
|
|
1099
|
+
message: '.sdd-meta.json: root is not an object',
|
|
1100
|
+
details: ['Next upgrade will regenerate it.'],
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// Validate schemaVersion. Absent = v1 (forward-compat). Newer than
|
|
1105
|
+
// supported = still WARN (fallback path handles it).
|
|
1106
|
+
const {
|
|
1107
|
+
CURRENT_SCHEMA_VERSION,
|
|
1108
|
+
expectedSmartDiffTrackedPaths,
|
|
1109
|
+
} = require('./meta');
|
|
1110
|
+
const schemaVersion = parsed.schemaVersion ?? 1;
|
|
1111
|
+
if (typeof schemaVersion !== 'number' || schemaVersion < 1) {
|
|
1112
|
+
return {
|
|
1113
|
+
status: WARN,
|
|
1114
|
+
message: `.sdd-meta.json: invalid schemaVersion ${schemaVersion}`,
|
|
1115
|
+
details: [],
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
if (schemaVersion > CURRENT_SCHEMA_VERSION) {
|
|
1119
|
+
return {
|
|
1120
|
+
status: WARN,
|
|
1121
|
+
message: `.sdd-meta.json: schemaVersion ${schemaVersion} newer than supported ${CURRENT_SCHEMA_VERSION}`,
|
|
1122
|
+
details: ['Upgrade the sdd-devflow CLI to the latest version.'],
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
const hashes = parsed.hashes;
|
|
1127
|
+
if (typeof hashes !== 'object' || hashes === null || Array.isArray(hashes)) {
|
|
1128
|
+
return {
|
|
1129
|
+
status: WARN,
|
|
1130
|
+
message: '.sdd-meta.json: hashes field is not an object',
|
|
1131
|
+
details: ['Next upgrade will regenerate it.'],
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// Validate each entry's shape.
|
|
1136
|
+
const HASH_RE = /^sha256:[0-9a-f]{64}$/;
|
|
1137
|
+
const issues = [];
|
|
1138
|
+
for (const [k, v] of Object.entries(hashes)) {
|
|
1139
|
+
if (typeof k !== 'string') {
|
|
1140
|
+
issues.push(`invalid key: ${typeof k}`);
|
|
1141
|
+
continue;
|
|
1142
|
+
}
|
|
1143
|
+
// Reject absolute paths and `..` traversal.
|
|
1144
|
+
if (k.startsWith('/') || k.includes('..')) {
|
|
1145
|
+
issues.push(`suspicious key: ${k}`);
|
|
1146
|
+
continue;
|
|
1147
|
+
}
|
|
1148
|
+
if (typeof v !== 'string' || !HASH_RE.test(v)) {
|
|
1149
|
+
issues.push(`invalid hash for ${k}`);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
if (issues.length > 0) {
|
|
1154
|
+
return {
|
|
1155
|
+
status: WARN,
|
|
1156
|
+
message: `.sdd-meta.json: ${issues.length} shape issue${issues.length > 1 ? 's' : ''}`,
|
|
1157
|
+
details: [...issues.slice(0, 5), 'Next upgrade will regenerate affected entries.'],
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
// Detect orphan entries (keys not expected for the current tool/type).
|
|
1162
|
+
const expected = expectedSmartDiffTrackedPaths(aiTools, projectType);
|
|
1163
|
+
const orphans = Object.keys(hashes).filter((k) => !expected.has(k));
|
|
1164
|
+
if (orphans.length > 0) {
|
|
1165
|
+
return {
|
|
1166
|
+
status: WARN,
|
|
1167
|
+
message: `.sdd-meta.json: ${orphans.length} orphan entr${orphans.length > 1 ? 'ies' : 'y'}`,
|
|
1168
|
+
details: [
|
|
1169
|
+
...orphans.slice(0, 5).map((o) => `Orphan: ${o}`),
|
|
1170
|
+
'Non-fatal — next upgrade will prune these automatically.',
|
|
1171
|
+
],
|
|
1172
|
+
};
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
return {
|
|
1176
|
+
status: PASS,
|
|
1177
|
+
message: `Provenance metadata: valid (${Object.keys(hashes).length} tracked files)`,
|
|
1178
|
+
details: [],
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1016
1182
|
module.exports = {
|
|
1017
1183
|
runDoctor,
|
|
1018
1184
|
printResults,
|
package/lib/generator.js
CHANGED
|
@@ -10,6 +10,7 @@ const {
|
|
|
10
10
|
BACKEND_AGENTS,
|
|
11
11
|
} = require('./config');
|
|
12
12
|
const { adaptAgentContentForProjectType } = require('./adapt-agents');
|
|
13
|
+
const { writeMeta, computeInstallHashes } = require('./meta');
|
|
13
14
|
|
|
14
15
|
function generate(config) {
|
|
15
16
|
const templateDir = path.join(__dirname, '..', 'template');
|
|
@@ -95,6 +96,13 @@ function generate(config) {
|
|
|
95
96
|
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
|
|
96
97
|
fs.writeFileSync(path.join(dest, '.sdd-version'), pkg.version + '\n', 'utf8');
|
|
97
98
|
|
|
99
|
+
// v0.17.0: write provenance hashes. Captures whatever generator.js's
|
|
100
|
+
// pipeline produced (lighter than init-generator — adaptAgentsForStack
|
|
101
|
+
// only, no stack-adaptations module). First upgrade re-adapts via the
|
|
102
|
+
// scanner-driven pipeline; the hash answers "did the user edit since
|
|
103
|
+
// install" precisely regardless of which pipeline wrote the content.
|
|
104
|
+
writeMeta(dest, computeInstallHashes(dest, config.aiTools, config.projectType));
|
|
105
|
+
|
|
98
106
|
// Show notes
|
|
99
107
|
const notes = collectNotes(config);
|
|
100
108
|
if (notes.length > 0) {
|