@clawplays/ospec-cli 1.0.1 → 1.0.2
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 +2 -0
- package/SKILL.md +7 -4
- package/agents/openai.yaml +1 -1
- package/dist/cli.js +1 -1
- package/dist/commands/LayoutCommand.js +2 -2
- package/dist/commands/SkillCommand.js +7 -3
- package/dist/commands/UpdateCommand.d.ts +8 -0
- package/dist/commands/UpdateCommand.js +241 -0
- package/dist/core/constants.d.ts +1 -0
- package/dist/core/constants.js +1 -0
- package/dist/core/types.d.ts +1 -0
- package/dist/services/ConfigManager.d.ts +2 -0
- package/dist/services/ConfigManager.js +18 -0
- package/dist/services/ProjectAssetService.d.ts +2 -0
- package/dist/services/ProjectAssetService.js +12 -0
- package/dist/services/ProjectService.d.ts +1 -0
- package/dist/services/ProjectService.js +102 -37
- package/dist/services/templates/ProjectTemplateBuilder.js +49 -55
- package/dist/services/templates/TemplateInputFactory.js +2 -1
- package/dist/utils/ProjectLayout.js +1 -0
- package/package.json +1 -1
- package/skill.yaml +1 -1
package/README.md
CHANGED
|
@@ -94,6 +94,7 @@ CLI notes:
|
|
|
94
94
|
- CLI language resolution order: explicit `--document-language` -> persisted project language in `.skillrc` -> existing project docs / managed `for-ai/*` guidance / asset manifest -> fallback `en-US`
|
|
95
95
|
- OSpec persists the chosen project document language in `.skillrc` and reuses it for `for-ai` guidance, `ospec new`, and `ospec update`
|
|
96
96
|
- new projects initialized by `ospec init` default to the nested layout: root `.skillrc` and `README.md`, with OSpec-managed files under `.ospec/`
|
|
97
|
+
- plain init does not create optional knowledge maps such as `.ospec/knowledge/src/` or `.ospec/knowledge/tests/`; those appear only when a project already has legacy knowledge content to migrate or when future explicit knowledge-generation flows create them
|
|
97
98
|
- CLI commands still accept shorthand like `changes/active/<change-name>`, but the physical path in nested projects is `.ospec/changes/active/<change-name>`
|
|
98
99
|
- if you pass these values, OSpec uses them directly when generating project docs
|
|
99
100
|
- if you do not pass them, OSpec reuses existing docs when possible and otherwise creates placeholder docs first
|
|
@@ -171,6 +172,7 @@ ospec update
|
|
|
171
172
|
|
|
172
173
|
`ospec update` also migrates legacy root-level `build-index-auto.cjs` / `build-index-auto.js` tooling into `.ospec/tools/build-index-auto.cjs` and refreshes OSpec-managed hook entrypoints to use the new location.
|
|
173
174
|
It also repairs older OSpec projects that still have an OSpec footprint but are missing newer core runtime directories, refreshes managed skills and archive layout metadata, and syncs project assets for already-enabled plugins.
|
|
175
|
+
For nested projects that still carry legacy knowledge under `.ospec/src/` or `.ospec/tests/`, `ospec update` migrates those paths into `.ospec/knowledge/src/` and `.ospec/knowledge/tests/`.
|
|
174
176
|
When an already-enabled plugin has a newer compatible npm package version available, `ospec update` upgrades that global plugin package automatically and prints the version transition.
|
|
175
177
|
It does not upgrade the CLI itself, and it does not enable plugins or migrate active / queued changes automatically.
|
|
176
178
|
It also does not switch a classic project layout to nested automatically.
|
package/SKILL.md
CHANGED
|
@@ -37,9 +37,10 @@ If the user intent is simply to initialize the project or current directory, tre
|
|
|
37
37
|
Use this exact behavior:
|
|
38
38
|
|
|
39
39
|
1. run `ospec init [path]` when the directory is uninitialized or not yet change-ready
|
|
40
|
-
2.
|
|
41
|
-
3.
|
|
42
|
-
4.
|
|
40
|
+
2. in AI-assisted init, map an explicit language request or the current conversation language to `--document-language`; do not assume a brand-new repo will infer it
|
|
41
|
+
3. if AI assistance is available and the repository lacks usable project context, ask one concise follow-up for summary or tech stack before init when helpful
|
|
42
|
+
4. verify the actual filesystem result before claiming initialization is complete
|
|
43
|
+
5. stop before `ospec new` unless the user explicitly asks to create a change
|
|
43
44
|
|
|
44
45
|
Never replace `ospec init` with manual directory creation or a hand-written approximation.
|
|
45
46
|
|
|
@@ -66,7 +67,7 @@ Required checks after `ospec init`:
|
|
|
66
67
|
- `docs/project/module-map.md`
|
|
67
68
|
- `docs/project/api-overview.md`
|
|
68
69
|
|
|
69
|
-
During plain init, do not report `docs/SKILL.md`, `src/SKILL.md
|
|
70
|
+
During plain init, do not report `docs/SKILL.md`, optional knowledge maps such as `knowledge/src/SKILL.md` / `knowledge/tests/SKILL.md`, or business scaffold as if they were part of change-ready completion.
|
|
70
71
|
|
|
71
72
|
## Prompt Profiles
|
|
72
73
|
|
|
@@ -133,6 +134,7 @@ Use ospec to create a change queue and execute it explicitly with ospec run manu
|
|
|
133
134
|
Always keep these rules:
|
|
134
135
|
|
|
135
136
|
- `ospec init` should leave the repository in a change-ready state
|
|
137
|
+
- in AI-assisted init, pass `--document-language` from the explicit language request or current conversation language when the project language is already apparent
|
|
136
138
|
- AI-assisted init may ask one concise follow-up question for missing summary or tech stack; if the user declines, continue with placeholder docs
|
|
137
139
|
- `ospec docs generate` refreshes, repairs, or backfills project knowledge docs after initialization
|
|
138
140
|
- when the user asks to initialize, execute the CLI init command and verify the protocol-shell files and `docs/project/*` files on disk before declaring success
|
|
@@ -184,6 +186,7 @@ Do not fall back to the old `features/...` layout unless the target repository r
|
|
|
184
186
|
```bash
|
|
185
187
|
ospec status [path]
|
|
186
188
|
ospec init [path]
|
|
189
|
+
ospec init [path] --document-language zh-CN
|
|
187
190
|
ospec init [path] --summary "..." --tech-stack node,react
|
|
188
191
|
ospec docs generate [path]
|
|
189
192
|
ospec new <change-name> [path]
|
package/agents/openai.yaml
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
interface:
|
|
2
2
|
display_name: "OSpec"
|
|
3
3
|
short_description: "Inspect, initialize, and operate OSpec projects"
|
|
4
|
-
default_prompt: "Use $ospec to initialize this directory according to OSpec rules: use ospec init so the repository ends in change-ready state, reuse existing project docs when available, ask one concise follow-up for missing summary or tech stack in AI-assisted flows, fall back to placeholder docs when the user skips that context, do not assume a web template when the project type is unclear, do not create the first change automatically, and use ospec status or ospec changes status only when you need an explicit summary."
|
|
4
|
+
default_prompt: "Use $ospec to initialize this directory according to OSpec rules: use ospec init so the repository ends in change-ready state, reuse existing project docs when available, map an explicit language request or the current conversation language to --document-language during AI-assisted init instead of assuming a brand-new repo will infer it, ask one concise follow-up for missing summary or tech stack in AI-assisted flows, fall back to placeholder docs when the user skips that context, do not assume a web template when the project type is unclear, do not create the first change automatically, and use ospec status or ospec changes status only when you need an explicit summary."
|
package/dist/cli.js
CHANGED
|
@@ -55,7 +55,7 @@ const VerifyCommand_1 = require("./commands/VerifyCommand");
|
|
|
55
55
|
const WorkflowCommand_1 = require("./commands/WorkflowCommand");
|
|
56
56
|
const LayoutCommand_1 = require("./commands/LayoutCommand");
|
|
57
57
|
const services_1 = require("./services");
|
|
58
|
-
const CLI_VERSION = '1.0.
|
|
58
|
+
const CLI_VERSION = '1.0.2';
|
|
59
59
|
function showInitUsage() {
|
|
60
60
|
console.log('Usage: ospec init [root-dir] [--summary "..."] [--tech-stack node,react] [--architecture "..."] [--document-language en-US|zh-CN|ja-JP|ar]');
|
|
61
61
|
}
|
|
@@ -159,16 +159,16 @@ class LayoutCommand extends BaseCommand_1.BaseCommand {
|
|
|
159
159
|
constants_1.FILE_NAMES.SKILL_INDEX,
|
|
160
160
|
'changes',
|
|
161
161
|
'for-ai',
|
|
162
|
+
'knowledge',
|
|
162
163
|
'docs/project',
|
|
163
164
|
'docs/design',
|
|
164
165
|
'docs/planning',
|
|
165
166
|
'docs/api',
|
|
166
167
|
'docs/SKILL.md',
|
|
167
|
-
'tests/SKILL.md',
|
|
168
168
|
]) {
|
|
169
169
|
plannedRelativePaths.add(relativePath);
|
|
170
170
|
}
|
|
171
|
-
for (const baseDir of ['src', 'docs', 'tests']) {
|
|
171
|
+
for (const baseDir of ['knowledge', 'src', 'docs', 'tests']) {
|
|
172
172
|
const absoluteBaseDir = path.join(rootDir, baseDir);
|
|
173
173
|
if (!(await services_1.services.fileService.exists(absoluteBaseDir))) {
|
|
174
174
|
continue;
|
|
@@ -16,7 +16,7 @@ const ACTION_SKILLS = [
|
|
|
16
16
|
title: 'OSpec Init',
|
|
17
17
|
description: 'Initialize an OSpec repository to change-ready state without creating the first change automatically.',
|
|
18
18
|
shortDescription: 'Initialize OSpec to change-ready',
|
|
19
|
-
defaultPrompt: 'Use $ospec-init to initialize the target directory with ospec init so the repository ends in change-ready state. Reuse existing project docs when available. If the repository lacks a usable project overview and you are in an AI-assisted flow, ask one concise question for project summary or tech stack before calling ospec init with those inputs; if the user declines, run plain ospec init and allow placeholder docs. Verify the protocol-shell files and project knowledge docs on disk. Do not create the first change automatically.',
|
|
19
|
+
defaultPrompt: 'Use $ospec-init to initialize the target directory with ospec init so the repository ends in change-ready state. Reuse existing project docs when available. In AI-assisted init, map an explicit language request or the current conversation language to --document-language instead of assuming a brand-new repo will infer it. If the repository lacks a usable project overview and you are in an AI-assisted flow, ask one concise question for project summary or tech stack before calling ospec init with those inputs; if the user declines, run plain ospec init and allow placeholder docs. Verify the protocol-shell files and project knowledge docs on disk. Do not create the first change automatically.',
|
|
20
20
|
markdown: `# OSpec Init
|
|
21
21
|
|
|
22
22
|
|
|
@@ -31,6 +31,8 @@ Use this action when the user intent is initialization.
|
|
|
31
31
|
|
|
32
32
|
- use \`ospec init [path]\` so the repository ends in change-ready state
|
|
33
33
|
|
|
34
|
+
- in AI-assisted init, map an explicit language request or the current conversation language to \`--document-language\`
|
|
35
|
+
|
|
34
36
|
- verify root \`.skillrc\` and \`README.md\`, plus managed OSpec files under \`.ospec/\` including \`.ospec/SKILL.md\`, \`.ospec/SKILL.index.json\`, \`.ospec/changes/\`, \`.ospec/tools/build-index-auto.cjs\`, \`.ospec/for-ai/\`, and \`.ospec/docs/project/\` on disk for new projects
|
|
35
37
|
|
|
36
38
|
- if project overview context is missing and AI can ask follow-up questions, ask for a brief summary or tech stack before initialization; if the user declines, fall back to placeholder docs
|
|
@@ -51,6 +53,8 @@ Use this action when the user intent is initialization.
|
|
|
51
53
|
|
|
52
54
|
ospec init [path]
|
|
53
55
|
|
|
56
|
+
ospec init [path] --document-language zh-CN
|
|
57
|
+
|
|
54
58
|
ospec init [path] --summary "..." --tech-stack node,react
|
|
55
59
|
|
|
56
60
|
ospec status [path]
|
|
@@ -841,7 +845,7 @@ interface:
|
|
|
841
845
|
|
|
842
846
|
short_description: "Legacy alias for the OSpec skill"
|
|
843
847
|
|
|
844
|
-
default_prompt: "Use $ospec to initialize this directory according to OSpec rules: init should end in change-ready state, reuse existing docs when available, ask for missing summary or tech stack in AI-assisted flows before falling back to placeholder docs, avoid assumed web templates when the project type is unclear, and do not create the first change automatically."
|
|
848
|
+
default_prompt: "Use $ospec to initialize this directory according to OSpec rules: init should end in change-ready state, reuse existing docs when available, map an explicit language request or the current conversation language to --document-language during AI-assisted init instead of assuming a brand-new repo will infer it, ask for missing summary or tech stack in AI-assisted flows before falling back to placeholder docs, avoid assumed web templates when the project type is unclear, and do not create the first change automatically."
|
|
845
849
|
|
|
846
850
|
`,
|
|
847
851
|
openaiYaml: `interface:
|
|
@@ -850,7 +854,7 @@ interface:
|
|
|
850
854
|
|
|
851
855
|
short_description: "Legacy alias for the OSpec skill"
|
|
852
856
|
|
|
853
|
-
default_prompt: "Use $ospec to initialize this directory according to OSpec rules: init should end in change-ready state, reuse existing docs when available, ask for missing summary or tech stack in AI-assisted flows before falling back to placeholder docs, avoid assumed web templates when the project type is unclear, and do not create the first change automatically."
|
|
857
|
+
default_prompt: "Use $ospec to initialize this directory according to OSpec rules: init should end in change-ready state, reuse existing docs when available, map an explicit language request or the current conversation language to --document-language during AI-assisted init instead of assuming a brand-new repo will infer it, ask for missing summary or tech stack in AI-assisted flows before falling back to placeholder docs, avoid assumed web templates when the project type is unclear, and do not create the first change automatically."
|
|
854
858
|
|
|
855
859
|
`,
|
|
856
860
|
};
|
|
@@ -5,6 +5,14 @@ export declare class UpdateCommand extends BaseCommand {
|
|
|
5
5
|
private repairLegacyProjectForUpdate;
|
|
6
6
|
private detectLegacyProjectMarkers;
|
|
7
7
|
private syncProjectTooling;
|
|
8
|
+
private migrateLegacyKnowledgeLayout;
|
|
9
|
+
private syncProjectCliVersionMetadata;
|
|
10
|
+
private detectProjectCliVersion;
|
|
11
|
+
private isLegacyKnowledgeMigrationEligible;
|
|
12
|
+
private isCliVersionAtLeast;
|
|
13
|
+
private mergeLegacyKnowledgeDirectory;
|
|
14
|
+
private refreshMigratedKnowledgeLinks;
|
|
15
|
+
private rewriteFileIfChanged;
|
|
8
16
|
private ensureEnabledPluginPackageAvailable;
|
|
9
17
|
private maybeUpgradeEnabledPluginPackage;
|
|
10
18
|
private refreshExternalPluginInstalledMetadata;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.UpdateCommand = void 0;
|
|
4
|
+
const fs_1 = require("fs");
|
|
4
5
|
const os_1 = require("os");
|
|
5
6
|
const path_1 = require("path");
|
|
6
7
|
const services_1 = require("../services");
|
|
@@ -22,12 +23,16 @@ class UpdateCommand extends BaseCommand_1.BaseCommand {
|
|
|
22
23
|
throw new Error('Project is not initialized. Run "ospec init" first.');
|
|
23
24
|
}
|
|
24
25
|
this.info(`Updating OSpec project at ${targetPath}`);
|
|
26
|
+
const cliVersionMetadataSync = await this.syncProjectCliVersionMetadata(targetPath);
|
|
27
|
+
const legacyKnowledgeMigration = await this.migrateLegacyKnowledgeLayout(targetPath, cliVersionMetadataSync.effectiveProjectCliVersion);
|
|
25
28
|
const protocolResult = await services_1.services.projectService.syncProtocolGuidance(targetPath);
|
|
26
29
|
const toolingResult = await this.syncProjectTooling(targetPath, protocolResult.documentLanguage);
|
|
27
30
|
const pluginResult = await this.syncEnabledPluginAssets(targetPath);
|
|
28
31
|
const archiveResult = await this.syncArchiveLayout(targetPath);
|
|
29
32
|
const skillResult = await this.syncInstalledSkills();
|
|
30
33
|
const refreshedFiles = Array.from(new Set([
|
|
34
|
+
...(cliVersionMetadataSync.configSaved ? ['.skillrc'] : []),
|
|
35
|
+
...legacyKnowledgeMigration.refreshedFiles,
|
|
31
36
|
...protocolResult.refreshedFiles,
|
|
32
37
|
...toolingResult.refreshedFiles,
|
|
33
38
|
...pluginResult.refreshedFiles,
|
|
@@ -49,6 +54,20 @@ class UpdateCommand extends BaseCommand_1.BaseCommand {
|
|
|
49
54
|
this.info(` legacy paths normalized: ${legacyRepair.refreshedPaths.join(', ')}`);
|
|
50
55
|
}
|
|
51
56
|
}
|
|
57
|
+
if (cliVersionMetadataSync.configSaved) {
|
|
58
|
+
this.info(' project CLI version metadata normalized: .skillrc');
|
|
59
|
+
}
|
|
60
|
+
if (legacyKnowledgeMigration.performed) {
|
|
61
|
+
if (legacyKnowledgeMigration.migratedPaths.length > 0) {
|
|
62
|
+
this.info(` legacy knowledge migrated: ${legacyKnowledgeMigration.migratedPaths.join(', ')}`);
|
|
63
|
+
}
|
|
64
|
+
if (legacyKnowledgeMigration.refreshedFiles.length > 0) {
|
|
65
|
+
this.info(` migrated knowledge links refreshed: ${legacyKnowledgeMigration.refreshedFiles.join(', ')}`);
|
|
66
|
+
}
|
|
67
|
+
if (legacyKnowledgeMigration.removedPaths.length > 0) {
|
|
68
|
+
this.info(` legacy knowledge paths removed: ${legacyKnowledgeMigration.removedPaths.join(', ')}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
52
71
|
if (toolingResult.hookInstalledFiles.length > 0) {
|
|
53
72
|
this.info(` git hooks refreshed: ${toolingResult.hookInstalledFiles.join(', ')}`);
|
|
54
73
|
}
|
|
@@ -205,6 +224,228 @@ class UpdateCommand extends BaseCommand_1.BaseCommand {
|
|
|
205
224
|
removedLegacyFiles,
|
|
206
225
|
};
|
|
207
226
|
}
|
|
227
|
+
async migrateLegacyKnowledgeLayout(rootDir, effectiveProjectCliVersion) {
|
|
228
|
+
const config = await services_1.services.configManager.loadConfig(rootDir).catch(() => null);
|
|
229
|
+
if (!(await this.isLegacyKnowledgeMigrationEligible(rootDir, config, effectiveProjectCliVersion))) {
|
|
230
|
+
return {
|
|
231
|
+
performed: false,
|
|
232
|
+
migratedPaths: [],
|
|
233
|
+
refreshedFiles: [],
|
|
234
|
+
removedPaths: [],
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
const knowledgeRoot = (0, path_1.join)(rootDir, '.ospec', 'knowledge');
|
|
238
|
+
const migrations = [
|
|
239
|
+
{
|
|
240
|
+
sourcePath: (0, path_1.join)(rootDir, '.ospec', 'src'),
|
|
241
|
+
targetPath: (0, path_1.join)(knowledgeRoot, 'src'),
|
|
242
|
+
sourceRelativePath: '.ospec/src',
|
|
243
|
+
targetRelativePath: '.ospec/knowledge/src',
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
sourcePath: (0, path_1.join)(rootDir, '.ospec', 'tests'),
|
|
247
|
+
targetPath: (0, path_1.join)(knowledgeRoot, 'tests'),
|
|
248
|
+
sourceRelativePath: '.ospec/tests',
|
|
249
|
+
targetRelativePath: '.ospec/knowledge/tests',
|
|
250
|
+
},
|
|
251
|
+
];
|
|
252
|
+
if (!(await Promise.all(migrations.map(item => services_1.services.fileService.exists(item.sourcePath)))).some(Boolean)) {
|
|
253
|
+
return {
|
|
254
|
+
performed: false,
|
|
255
|
+
migratedPaths: [],
|
|
256
|
+
refreshedFiles: [],
|
|
257
|
+
removedPaths: [],
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
await services_1.services.fileService.ensureDir(knowledgeRoot);
|
|
261
|
+
const migratedPaths = [];
|
|
262
|
+
const removedPaths = [];
|
|
263
|
+
for (const migration of migrations) {
|
|
264
|
+
if (!(await services_1.services.fileService.exists(migration.sourcePath))) {
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
if (!(await services_1.services.fileService.exists(migration.targetPath))) {
|
|
268
|
+
await services_1.services.fileService.move(migration.sourcePath, migration.targetPath);
|
|
269
|
+
migratedPaths.push(`${migration.sourceRelativePath} -> ${migration.targetRelativePath}`);
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
await this.mergeLegacyKnowledgeDirectory(migration.sourcePath, migration.targetPath);
|
|
273
|
+
await services_1.services.fileService.remove(migration.sourcePath);
|
|
274
|
+
migratedPaths.push(`${migration.sourceRelativePath} -> ${migration.targetRelativePath}`);
|
|
275
|
+
removedPaths.push(migration.sourceRelativePath);
|
|
276
|
+
}
|
|
277
|
+
const refreshedFiles = await this.refreshMigratedKnowledgeLinks(rootDir);
|
|
278
|
+
return {
|
|
279
|
+
performed: migratedPaths.length > 0 || refreshedFiles.length > 0 || removedPaths.length > 0,
|
|
280
|
+
migratedPaths,
|
|
281
|
+
refreshedFiles,
|
|
282
|
+
removedPaths,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
async syncProjectCliVersionMetadata(rootDir) {
|
|
286
|
+
const config = await services_1.services.configManager.loadConfig(rootDir).catch(() => null);
|
|
287
|
+
if (!config) {
|
|
288
|
+
return {
|
|
289
|
+
configSaved: false,
|
|
290
|
+
effectiveProjectCliVersion: null,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
const detectedProjectCliVersion = await this.detectProjectCliVersion(rootDir, config);
|
|
294
|
+
if (typeof config.ospecCliVersion === 'string' && config.ospecCliVersion.trim().length > 0) {
|
|
295
|
+
return {
|
|
296
|
+
configSaved: false,
|
|
297
|
+
effectiveProjectCliVersion: config.ospecCliVersion.trim(),
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
if (!detectedProjectCliVersion) {
|
|
301
|
+
return {
|
|
302
|
+
configSaved: false,
|
|
303
|
+
effectiveProjectCliVersion: null,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
await services_1.services.configManager.saveConfig(rootDir, {
|
|
307
|
+
...config,
|
|
308
|
+
ospecCliVersion: detectedProjectCliVersion,
|
|
309
|
+
});
|
|
310
|
+
return {
|
|
311
|
+
configSaved: true,
|
|
312
|
+
effectiveProjectCliVersion: detectedProjectCliVersion,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
async detectProjectCliVersion(rootDir, config) {
|
|
316
|
+
if (typeof config?.ospecCliVersion === 'string' && config.ospecCliVersion.trim().length > 0) {
|
|
317
|
+
return config.ospecCliVersion.trim();
|
|
318
|
+
}
|
|
319
|
+
const assetManifestPath = (0, path_1.join)(rootDir, '.ospec', 'asset-sources.json');
|
|
320
|
+
if (await services_1.services.fileService.exists(assetManifestPath)) {
|
|
321
|
+
try {
|
|
322
|
+
const assetManifest = await services_1.services.fileService.readJSON(assetManifestPath);
|
|
323
|
+
if (typeof assetManifest?.ospecCliVersion === 'string' && assetManifest.ospecCliVersion.trim().length > 0) {
|
|
324
|
+
return assetManifest.ospecCliVersion.trim();
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
return '1.0.0';
|
|
331
|
+
}
|
|
332
|
+
const legacyKnowledgeRoots = [
|
|
333
|
+
(0, path_1.join)(rootDir, '.ospec', 'src'),
|
|
334
|
+
(0, path_1.join)(rootDir, '.ospec', 'tests'),
|
|
335
|
+
];
|
|
336
|
+
if ((await Promise.all(legacyKnowledgeRoots.map(target => services_1.services.fileService.exists(target)))).some(Boolean)) {
|
|
337
|
+
return '1.0.0';
|
|
338
|
+
}
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
async isLegacyKnowledgeMigrationEligible(rootDir, config, effectiveProjectCliVersion) {
|
|
342
|
+
if (config?.projectLayout !== 'nested') {
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
if (!effectiveProjectCliVersion || !this.isCliVersionAtLeast(effectiveProjectCliVersion, '1.0.0')) {
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
isCliVersionAtLeast(version, minimum) {
|
|
351
|
+
const parse = (value) => String(value || '')
|
|
352
|
+
.trim()
|
|
353
|
+
.replace(/^v/i, '')
|
|
354
|
+
.split('-', 1)[0]
|
|
355
|
+
.split('.')
|
|
356
|
+
.map(part => Number.parseInt(part, 10));
|
|
357
|
+
const left = parse(version);
|
|
358
|
+
const right = parse(minimum);
|
|
359
|
+
for (let index = 0; index < Math.max(left.length, right.length, 3); index += 1) {
|
|
360
|
+
const leftPart = Number.isFinite(left[index]) ? left[index] : 0;
|
|
361
|
+
const rightPart = Number.isFinite(right[index]) ? right[index] : 0;
|
|
362
|
+
if (leftPart > rightPart) {
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
if (leftPart < rightPart) {
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
371
|
+
async mergeLegacyKnowledgeDirectory(sourceDir, targetDir) {
|
|
372
|
+
await services_1.services.fileService.ensureDir(targetDir);
|
|
373
|
+
const entries = await fs_1.promises.readdir(sourceDir, { withFileTypes: true });
|
|
374
|
+
for (const entry of entries) {
|
|
375
|
+
const sourcePath = (0, path_1.join)(sourceDir, entry.name);
|
|
376
|
+
const targetPath = (0, path_1.join)(targetDir, entry.name);
|
|
377
|
+
if (entry.isDirectory()) {
|
|
378
|
+
await this.mergeLegacyKnowledgeDirectory(sourcePath, targetPath);
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
if (await services_1.services.fileService.exists(targetPath)) {
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
await services_1.services.fileService.move(sourcePath, targetPath);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
async refreshMigratedKnowledgeLinks(rootDir) {
|
|
388
|
+
const refreshedFiles = [];
|
|
389
|
+
const knowledgeSourceRoot = (0, path_1.join)(rootDir, '.ospec', 'knowledge', 'src');
|
|
390
|
+
const rewrites = [
|
|
391
|
+
{
|
|
392
|
+
filePath: (0, path_1.join)(rootDir, '.ospec', 'SKILL.md'),
|
|
393
|
+
transform: content => content
|
|
394
|
+
.replace(/\]\(src\/SKILL\.md\)/g, '](knowledge/src/SKILL.md)')
|
|
395
|
+
.replace(/\]\(tests\/SKILL\.md\)/g, '](knowledge/tests/SKILL.md)'),
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
filePath: (0, path_1.join)(rootDir, '.ospec', 'docs', 'project', 'module-map.md'),
|
|
399
|
+
transform: content => content.replace(/\(src\/modules\//g, '(../../knowledge/src/modules/'),
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
filePath: (0, path_1.join)(knowledgeSourceRoot, 'SKILL.md'),
|
|
403
|
+
transform: content => content
|
|
404
|
+
.replace(/src\/SKILL\.md/g, 'knowledge/src/SKILL.md')
|
|
405
|
+
.replace(/`src\/modules\/<module>\/SKILL\.md`/g, '`knowledge/src/modules/<module>/SKILL.md`'),
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
filePath: (0, path_1.join)(knowledgeSourceRoot, 'core', 'SKILL.md'),
|
|
409
|
+
transform: content => content
|
|
410
|
+
.replace(/src\/SKILL\.md/g, 'knowledge/src/SKILL.md')
|
|
411
|
+
.replace(/\.\.\/\.\.\/docs\/project\//g, '../../../docs/project/'),
|
|
412
|
+
},
|
|
413
|
+
];
|
|
414
|
+
for (const rewrite of rewrites) {
|
|
415
|
+
if (await this.rewriteFileIfChanged(rewrite.filePath, rewrite.transform)) {
|
|
416
|
+
refreshedFiles.push(this.toRelativePath(rootDir, rewrite.filePath));
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
const moduleSkillsRoot = (0, path_1.join)(knowledgeSourceRoot, 'modules');
|
|
420
|
+
if (!(await services_1.services.fileService.exists(moduleSkillsRoot))) {
|
|
421
|
+
return refreshedFiles;
|
|
422
|
+
}
|
|
423
|
+
const moduleEntries = await fs_1.promises.readdir(moduleSkillsRoot, { withFileTypes: true });
|
|
424
|
+
for (const entry of moduleEntries) {
|
|
425
|
+
if (!entry.isDirectory()) {
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
const skillPath = (0, path_1.join)(moduleSkillsRoot, entry.name, 'SKILL.md');
|
|
429
|
+
if (await this.rewriteFileIfChanged(skillPath, content => content
|
|
430
|
+
.replace(/src\/SKILL\.md/g, 'knowledge/src/SKILL.md')
|
|
431
|
+
.replace(/\.\.\/\.\.\/\.\.\/docs\/project\//g, '../../../../docs/project/'))) {
|
|
432
|
+
refreshedFiles.push(this.toRelativePath(rootDir, skillPath));
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return refreshedFiles;
|
|
436
|
+
}
|
|
437
|
+
async rewriteFileIfChanged(filePath, transform) {
|
|
438
|
+
if (!(await services_1.services.fileService.exists(filePath))) {
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
const before = await services_1.services.fileService.readFile(filePath);
|
|
442
|
+
const after = transform(before);
|
|
443
|
+
if (after === before) {
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
await services_1.services.fileService.writeFile(filePath, after);
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
208
449
|
async syncEnabledPluginAssets(rootDir) {
|
|
209
450
|
const rawConfig = await services_1.services.fileService.readJSON((0, path_1.join)(rootDir, '.skillrc'));
|
|
210
451
|
const config = await services_1.services.configManager.loadConfig(rootDir);
|
package/dist/core/constants.d.ts
CHANGED
package/dist/core/constants.js
CHANGED
package/dist/core/types.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export declare class ConfigManager {
|
|
|
8
8
|
getMode(rootDir: string): Promise<ProjectMode>;
|
|
9
9
|
isInitialized(rootDir: string): Promise<boolean>;
|
|
10
10
|
createDefaultConfig(mode?: ProjectMode): Promise<SkillrcConfig>;
|
|
11
|
+
private normalizeCliVersion;
|
|
11
12
|
private normalizeConfig;
|
|
13
|
+
private getPackageVersion;
|
|
12
14
|
}
|
|
13
15
|
export declare function createConfigManager(fileService: FileService): ConfigManager;
|
|
@@ -72,6 +72,7 @@ class ConfigManager {
|
|
|
72
72
|
return {
|
|
73
73
|
version: '4.0',
|
|
74
74
|
mode,
|
|
75
|
+
ospecCliVersion: await this.getPackageVersion() || undefined,
|
|
75
76
|
projectLayout: 'nested',
|
|
76
77
|
hooks: {
|
|
77
78
|
'pre-commit': true,
|
|
@@ -257,6 +258,11 @@ class ConfigManager {
|
|
|
257
258
|
? input
|
|
258
259
|
: undefined;
|
|
259
260
|
}
|
|
261
|
+
normalizeCliVersion(input) {
|
|
262
|
+
return typeof input === 'string' && input.trim().length > 0
|
|
263
|
+
? input.trim()
|
|
264
|
+
: undefined;
|
|
265
|
+
}
|
|
260
266
|
normalizePluginsConfig(plugins) {
|
|
261
267
|
const defaults = this.createDefaultPluginsConfig();
|
|
262
268
|
const stitch = plugins?.stitch && typeof plugins.stitch === 'object' ? plugins.stitch : {};
|
|
@@ -640,6 +646,7 @@ class ConfigManager {
|
|
|
640
646
|
...config,
|
|
641
647
|
version: config.version === '3.0' ? '4.0' : config.version,
|
|
642
648
|
mode,
|
|
649
|
+
ospecCliVersion: this.normalizeCliVersion(config.ospecCliVersion),
|
|
643
650
|
projectLayout: (0, ProjectLayout_1.normalizeProjectLayout)(config.projectLayout) || 'classic',
|
|
644
651
|
documentLanguage: this.normalizeDocumentLanguage(config.documentLanguage),
|
|
645
652
|
archive: {
|
|
@@ -659,6 +666,17 @@ class ConfigManager {
|
|
|
659
666
|
workflow: config.workflow || ConfigurableWorkflow_1.WORKFLOW_PRESETS[mode],
|
|
660
667
|
};
|
|
661
668
|
}
|
|
669
|
+
async getPackageVersion() {
|
|
670
|
+
try {
|
|
671
|
+
const packageJson = await this.fileService.readJSON(path.join(path.resolve(__dirname, '../..'), 'package.json'));
|
|
672
|
+
return typeof packageJson.version === 'string' && packageJson.version.trim().length > 0
|
|
673
|
+
? packageJson.version.trim()
|
|
674
|
+
: null;
|
|
675
|
+
}
|
|
676
|
+
catch {
|
|
677
|
+
return null;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
662
680
|
}
|
|
663
681
|
exports.ConfigManager = ConfigManager;
|
|
664
682
|
function createConfigManager(fileService) {
|
|
@@ -4,6 +4,7 @@ import { ProjectLayout } from '../core/types';
|
|
|
4
4
|
interface AssetManifestOptions {
|
|
5
5
|
projectLayout?: ProjectLayout;
|
|
6
6
|
documentLanguage?: string;
|
|
7
|
+
ospecCliVersion?: string;
|
|
7
8
|
templateGeneratedPaths: string[];
|
|
8
9
|
runtimeGeneratedPaths: string[];
|
|
9
10
|
}
|
|
@@ -45,6 +46,7 @@ export declare class ProjectAssetService {
|
|
|
45
46
|
private resolveStaticSourceHint;
|
|
46
47
|
private normalizePaths;
|
|
47
48
|
private getPackageRoot;
|
|
49
|
+
private getPackageVersion;
|
|
48
50
|
private isOSpecManagedHook;
|
|
49
51
|
}
|
|
50
52
|
export declare const createProjectAssetService: (fileService: FileService) => ProjectAssetService;
|
|
@@ -170,6 +170,7 @@ class ProjectAssetService {
|
|
|
170
170
|
const manifest = {
|
|
171
171
|
version: '1.0',
|
|
172
172
|
generatedAt: new Date().toISOString(),
|
|
173
|
+
ospecCliVersion: options.ospecCliVersion || (await this.getPackageVersion()) || undefined,
|
|
173
174
|
projectLayout,
|
|
174
175
|
documentLanguage: options.documentLanguage || 'en-US',
|
|
175
176
|
assets,
|
|
@@ -226,6 +227,17 @@ class ProjectAssetService {
|
|
|
226
227
|
getPackageRoot() {
|
|
227
228
|
return path_1.default.resolve(__dirname, '../..');
|
|
228
229
|
}
|
|
230
|
+
async getPackageVersion() {
|
|
231
|
+
try {
|
|
232
|
+
const packageJson = await this.fileService.readJSON(path_1.default.join(this.getPackageRoot(), 'package.json'));
|
|
233
|
+
return typeof packageJson.version === 'string' && packageJson.version.trim().length > 0
|
|
234
|
+
? packageJson.version.trim()
|
|
235
|
+
: null;
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
229
241
|
isOSpecManagedHook(content) {
|
|
230
242
|
return (content.includes('.ospec/tools/build-index-auto.cjs') ||
|
|
231
243
|
content.includes('build-index-auto.cjs') ||
|
|
@@ -187,6 +187,7 @@ export declare class ProjectService {
|
|
|
187
187
|
private applyProjectScaffoldPhase;
|
|
188
188
|
private getProtocolShellTemplateGeneratedPaths;
|
|
189
189
|
private getFullBootstrapTemplateGeneratedPaths;
|
|
190
|
+
private getExistingOptionalKnowledgeGeneratedPaths;
|
|
190
191
|
private getBootstrapAssetPlan;
|
|
191
192
|
private renderProtocolShellRootSkill;
|
|
192
193
|
private writeBootstrapSummary;
|
|
@@ -57,8 +57,12 @@ class ProjectService {
|
|
|
57
57
|
}
|
|
58
58
|
await this.projectAssetService.writeAssetManifest(rootDir, {
|
|
59
59
|
documentLanguage: normalized.documentLanguage,
|
|
60
|
+
ospecCliVersion: config.ospecCliVersion,
|
|
60
61
|
projectLayout: this.getProjectLayout(config),
|
|
61
|
-
templateGeneratedPaths:
|
|
62
|
+
templateGeneratedPaths: [
|
|
63
|
+
...this.getFullBootstrapTemplateGeneratedPaths(normalized),
|
|
64
|
+
...(await this.getExistingOptionalKnowledgeGeneratedPaths(rootDir, config)),
|
|
65
|
+
],
|
|
62
66
|
runtimeGeneratedPaths: [
|
|
63
67
|
constants_1.FILE_NAMES.SKILLRC,
|
|
64
68
|
constants_1.FILE_NAMES.SKILL_INDEX,
|
|
@@ -157,7 +161,12 @@ class ProjectService {
|
|
|
157
161
|
];
|
|
158
162
|
await this.projectAssetService.writeAssetManifest(rootDir, {
|
|
159
163
|
documentLanguage: normalized.documentLanguage,
|
|
160
|
-
|
|
164
|
+
ospecCliVersion: config.ospecCliVersion,
|
|
165
|
+
projectLayout: this.getProjectLayout(config),
|
|
166
|
+
templateGeneratedPaths: [
|
|
167
|
+
...this.getFullBootstrapTemplateGeneratedPaths(normalized),
|
|
168
|
+
...(await this.getExistingOptionalKnowledgeGeneratedPaths(rootDir, config)),
|
|
169
|
+
],
|
|
161
170
|
runtimeGeneratedPaths: runtimeGeneratedFiles,
|
|
162
171
|
});
|
|
163
172
|
return {
|
|
@@ -230,7 +239,12 @@ class ProjectService {
|
|
|
230
239
|
const assetPlan = this.getBootstrapAssetPlan(normalized.documentLanguage, normalized, { projectLayout: 'nested' });
|
|
231
240
|
await this.projectAssetService.writeAssetManifest(rootDir, {
|
|
232
241
|
documentLanguage: normalized.documentLanguage,
|
|
233
|
-
|
|
242
|
+
ospecCliVersion: config.ospecCliVersion,
|
|
243
|
+
projectLayout: this.getProjectLayout(config),
|
|
244
|
+
templateGeneratedPaths: [
|
|
245
|
+
...assetPlan.templateGeneratedFiles,
|
|
246
|
+
...(await this.getExistingOptionalKnowledgeGeneratedPaths(rootDir, config)),
|
|
247
|
+
],
|
|
234
248
|
runtimeGeneratedPaths: assetPlan.runtimeGeneratedFiles,
|
|
235
249
|
});
|
|
236
250
|
return {
|
|
@@ -260,6 +274,8 @@ class ProjectService {
|
|
|
260
274
|
}
|
|
261
275
|
await this.projectAssetService.writeAssetManifest(rootDir, {
|
|
262
276
|
documentLanguage: normalized.documentLanguage,
|
|
277
|
+
ospecCliVersion: config.ospecCliVersion,
|
|
278
|
+
projectLayout: this.getProjectLayout(config),
|
|
263
279
|
templateGeneratedPaths: this.getProtocolShellTemplateGeneratedPaths(),
|
|
264
280
|
runtimeGeneratedPaths: [
|
|
265
281
|
constants_1.FILE_NAMES.SKILLRC,
|
|
@@ -422,19 +438,30 @@ class ProjectService {
|
|
|
422
438
|
}
|
|
423
439
|
async scanModules(rootDir) {
|
|
424
440
|
const config = await this.configManager.loadConfig(rootDir).catch(() => null);
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
|
|
441
|
+
const modules = new Map();
|
|
442
|
+
const moduleDirectoryCandidates = [
|
|
443
|
+
this.resolveManagedPath(rootDir, `${constants_1.DIR_NAMES.KNOWLEDGE}/${constants_1.DIR_NAMES.SRC}/${constants_1.DIR_NAMES.MODULES}`, config),
|
|
444
|
+
this.resolveManagedPath(rootDir, `${constants_1.DIR_NAMES.SRC}/${constants_1.DIR_NAMES.MODULES}`, config),
|
|
445
|
+
];
|
|
446
|
+
for (const modulesDir of moduleDirectoryCandidates) {
|
|
447
|
+
if (!(await this.fileService.exists(modulesDir))) {
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
const entries = await fs_1.promises.readdir(modulesDir, { withFileTypes: true });
|
|
451
|
+
for (const entry of entries) {
|
|
452
|
+
if (!entry.isDirectory() || modules.has(entry.name)) {
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
const skillPath = path_1.default.join(modulesDir, entry.name, constants_1.FILE_NAMES.SKILL_MD);
|
|
456
|
+
modules.set(entry.name, {
|
|
457
|
+
name: entry.name,
|
|
458
|
+
path: path_1.default.join(modulesDir, entry.name),
|
|
459
|
+
skillPath,
|
|
460
|
+
skillExists: (0, fs_1.existsSync)(skillPath),
|
|
461
|
+
});
|
|
462
|
+
}
|
|
428
463
|
}
|
|
429
|
-
|
|
430
|
-
return entries
|
|
431
|
-
.filter(entry => entry.isDirectory())
|
|
432
|
-
.map(entry => ({
|
|
433
|
-
name: entry.name,
|
|
434
|
-
path: path_1.default.join(modulesDir, entry.name),
|
|
435
|
-
skillPath: path_1.default.join(modulesDir, entry.name, constants_1.FILE_NAMES.SKILL_MD),
|
|
436
|
-
skillExists: (0, fs_1.existsSync)(path_1.default.join(modulesDir, entry.name, constants_1.FILE_NAMES.SKILL_MD)),
|
|
437
|
-
}));
|
|
464
|
+
return Array.from(modules.values()).sort((left, right) => left.name.localeCompare(right.name));
|
|
438
465
|
}
|
|
439
466
|
async scanApiDocs(rootDir) {
|
|
440
467
|
return this.scanDocsInDirectory(rootDir, constants_1.DIR_NAMES.API);
|
|
@@ -948,6 +975,31 @@ class ProjectService {
|
|
|
948
975
|
await this.configManager.saveConfig(rootDir, config);
|
|
949
976
|
return true;
|
|
950
977
|
}
|
|
978
|
+
inferDocumentLanguageFromBootstrapInput(input) {
|
|
979
|
+
if (!input || this.normalizeDocumentLanguage(input.documentLanguage)) {
|
|
980
|
+
return undefined;
|
|
981
|
+
}
|
|
982
|
+
const descriptiveTexts = [
|
|
983
|
+
input.summary,
|
|
984
|
+
input.architecture,
|
|
985
|
+
...(Array.isArray(input.apiAreas) ? input.apiAreas : []),
|
|
986
|
+
...(Array.isArray(input.designDocs) ? input.designDocs : []),
|
|
987
|
+
...(Array.isArray(input.planningDocs) ? input.planningDocs : []),
|
|
988
|
+
input.projectName,
|
|
989
|
+
];
|
|
990
|
+
const descriptiveLanguage = this.detectDocumentLanguageFromTexts(descriptiveTexts);
|
|
991
|
+
if (descriptiveLanguage && descriptiveLanguage !== 'en-US') {
|
|
992
|
+
return descriptiveLanguage;
|
|
993
|
+
}
|
|
994
|
+
const localizedStructuralTexts = [
|
|
995
|
+
...(Array.isArray(input.techStack) ? input.techStack : []),
|
|
996
|
+
...(Array.isArray(input.modules) ? input.modules : []),
|
|
997
|
+
].filter(item => {
|
|
998
|
+
const detected = this.detectDocumentLanguageFromText(item);
|
|
999
|
+
return detected && detected !== 'en-US';
|
|
1000
|
+
});
|
|
1001
|
+
return this.detectDocumentLanguageFromTexts(localizedStructuralTexts);
|
|
1002
|
+
}
|
|
951
1003
|
detectDocumentLanguageFromTexts(contents) {
|
|
952
1004
|
const detectionCounts = new Map();
|
|
953
1005
|
const firstSeenOrder = new Map();
|
|
@@ -1080,7 +1132,7 @@ class ProjectService {
|
|
|
1080
1132
|
apiAreas: normalized.apiAreas,
|
|
1081
1133
|
designDocs: normalized.designDocs,
|
|
1082
1134
|
planningDocs: normalized.planningDocs,
|
|
1083
|
-
moduleSkillFiles:
|
|
1135
|
+
moduleSkillFiles: [],
|
|
1084
1136
|
moduleApiDocFiles: normalized.moduleApiPlans.map(plan => plan.path),
|
|
1085
1137
|
apiDocFiles: normalized.apiAreaPlans.map(plan => plan.path),
|
|
1086
1138
|
designDocFiles: normalized.designDocPlans.map(plan => plan.path),
|
|
@@ -1108,15 +1160,11 @@ class ProjectService {
|
|
|
1108
1160
|
`${constants_1.DIR_NAMES.DOCS}/${constants_1.DIR_NAMES.DESIGN}/README.md`,
|
|
1109
1161
|
`${constants_1.DIR_NAMES.DOCS}/${constants_1.DIR_NAMES.PLANNING}/README.md`,
|
|
1110
1162
|
`${constants_1.DIR_NAMES.DOCS}/${constants_1.DIR_NAMES.API}/README.md`,
|
|
1111
|
-
`${constants_1.DIR_NAMES.SRC}/${constants_1.FILE_NAMES.SKILL_MD}`,
|
|
1112
|
-
`${constants_1.DIR_NAMES.SRC}/${constants_1.DIR_NAMES.CORE}/${constants_1.FILE_NAMES.SKILL_MD}`,
|
|
1113
|
-
`${constants_1.DIR_NAMES.TESTS}/${constants_1.FILE_NAMES.SKILL_MD}`,
|
|
1114
1163
|
`${constants_1.DIR_NAMES.FOR_AI}/${constants_1.FILE_NAMES.AI_GUIDE}`,
|
|
1115
1164
|
`${constants_1.DIR_NAMES.FOR_AI}/${constants_1.FILE_NAMES.EXECUTION_PROTOCOL}`,
|
|
1116
1165
|
'.ospec/asset-sources.json',
|
|
1117
1166
|
...this.projectAssetService.getDirectCopyTargetPaths(),
|
|
1118
1167
|
...(scaffoldPlan?.files || []).map(file => file.path),
|
|
1119
|
-
...normalized.modulePlans.map(plan => plan.path),
|
|
1120
1168
|
...normalized.moduleApiPlans.map(plan => plan.path),
|
|
1121
1169
|
...normalized.apiAreaPlans.map(plan => plan.path),
|
|
1122
1170
|
...normalized.designDocPlans.map(plan => plan.path),
|
|
@@ -1140,9 +1188,6 @@ class ProjectService {
|
|
|
1140
1188
|
this.resolveManagedPath(rootDir, `${constants_1.DIR_NAMES.DOCS}/${constants_1.DIR_NAMES.DESIGN}`, config),
|
|
1141
1189
|
this.resolveManagedPath(rootDir, `${constants_1.DIR_NAMES.DOCS}/${constants_1.DIR_NAMES.PLANNING}`, config),
|
|
1142
1190
|
this.resolveManagedPath(rootDir, `${constants_1.DIR_NAMES.DOCS}/${constants_1.DIR_NAMES.API}`, config),
|
|
1143
|
-
this.resolveManagedPath(rootDir, `${constants_1.DIR_NAMES.SRC}/${constants_1.DIR_NAMES.CORE}`, config),
|
|
1144
|
-
this.resolveManagedPath(rootDir, `${constants_1.DIR_NAMES.SRC}/${constants_1.DIR_NAMES.MODULES}`, config),
|
|
1145
|
-
this.resolveManagedPath(rootDir, constants_1.DIR_NAMES.TESTS, config),
|
|
1146
1191
|
this.resolveManagedPath(rootDir, constants_1.DIR_NAMES.FOR_AI, config),
|
|
1147
1192
|
];
|
|
1148
1193
|
}
|
|
@@ -1161,9 +1206,6 @@ class ProjectService {
|
|
|
1161
1206
|
this.resolveManagedPath(rootDir, `${constants_1.DIR_NAMES.DOCS}/${constants_1.DIR_NAMES.DESIGN}`, config),
|
|
1162
1207
|
this.resolveManagedPath(rootDir, `${constants_1.DIR_NAMES.DOCS}/${constants_1.DIR_NAMES.PLANNING}`, config),
|
|
1163
1208
|
this.resolveManagedPath(rootDir, `${constants_1.DIR_NAMES.DOCS}/${constants_1.DIR_NAMES.API}`, config),
|
|
1164
|
-
this.resolveManagedPath(rootDir, `${constants_1.DIR_NAMES.SRC}/${constants_1.DIR_NAMES.CORE}`, config),
|
|
1165
|
-
this.resolveManagedPath(rootDir, `${constants_1.DIR_NAMES.SRC}/${constants_1.DIR_NAMES.MODULES}`, config),
|
|
1166
|
-
this.resolveManagedPath(rootDir, constants_1.DIR_NAMES.TESTS, config),
|
|
1167
1209
|
];
|
|
1168
1210
|
}
|
|
1169
1211
|
getMinimumRuntimeStructureDefinitions() {
|
|
@@ -1335,6 +1377,12 @@ class ProjectService {
|
|
|
1335
1377
|
...(await this.getBootstrapUpgradePlan(rootDir)),
|
|
1336
1378
|
...(input ?? {}),
|
|
1337
1379
|
};
|
|
1380
|
+
if (!this.normalizeDocumentLanguage(mergedInput.documentLanguage)) {
|
|
1381
|
+
const inferredInputDocumentLanguage = this.inferDocumentLanguageFromBootstrapInput(input);
|
|
1382
|
+
if (inferredInputDocumentLanguage) {
|
|
1383
|
+
mergedInput.documentLanguage = inferredInputDocumentLanguage;
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1338
1386
|
const inferredDefaults = {
|
|
1339
1387
|
modules: await this.inferBootstrapModules(rootDir),
|
|
1340
1388
|
};
|
|
@@ -1352,9 +1400,6 @@ class ProjectService {
|
|
|
1352
1400
|
const defaultLayout = this.getProjectLayout(config || { projectLayout: 'nested' });
|
|
1353
1401
|
await this.writeGeneratedFile(rootDir, this.resolveManagedPath(rootDir, constants_1.FILE_NAMES.SKILL_MD, defaultLayout), this.templateEngine.generateRootSkillTemplate(projectName, mode, normalized), result, { overwriteProtocolShellRootSkill: true });
|
|
1354
1402
|
await this.writeGeneratedFile(rootDir, this.resolveManagedPath(rootDir, `${constants_1.DIR_NAMES.DOCS}/${constants_1.FILE_NAMES.SKILL_MD}`, defaultLayout), this.templateEngine.generateDocsSkillTemplate(projectName, normalized), result);
|
|
1355
|
-
await this.writeGeneratedFile(rootDir, this.resolveManagedPath(rootDir, `${constants_1.DIR_NAMES.SRC}/${constants_1.FILE_NAMES.SKILL_MD}`, defaultLayout), this.templateEngine.generateSrcSkillTemplate(projectName, normalized), result);
|
|
1356
|
-
await this.writeGeneratedFile(rootDir, this.resolveManagedPath(rootDir, `${constants_1.DIR_NAMES.SRC}/${constants_1.DIR_NAMES.CORE}/${constants_1.FILE_NAMES.SKILL_MD}`, defaultLayout), this.templateEngine.generateCoreSkillTemplate(projectName, normalized), result);
|
|
1357
|
-
await this.writeGeneratedFile(rootDir, this.resolveManagedPath(rootDir, `${constants_1.DIR_NAMES.TESTS}/${constants_1.FILE_NAMES.SKILL_MD}`, defaultLayout), this.templateEngine.generateTestsSkillTemplate(projectName, normalized), result);
|
|
1358
1403
|
await this.writeGeneratedFile(rootDir, this.resolveManagedPath(rootDir, `${constants_1.DIR_NAMES.DOCS}/${constants_1.DIR_NAMES.PROJECT}/overview.md`, defaultLayout), this.templateEngine.generateProjectOverviewTemplate(projectName, mode, normalized), result);
|
|
1359
1404
|
await this.writeGeneratedFile(rootDir, this.resolveManagedPath(rootDir, `${constants_1.DIR_NAMES.DOCS}/${constants_1.DIR_NAMES.PROJECT}/tech-stack.md`, defaultLayout), this.templateEngine.generateTechStackTemplate(projectName, normalized), result);
|
|
1360
1405
|
await this.writeGeneratedFile(rootDir, this.resolveManagedPath(rootDir, `${constants_1.DIR_NAMES.DOCS}/${constants_1.DIR_NAMES.PROJECT}/architecture.md`, defaultLayout), this.templateEngine.generateArchitectureTemplate(projectName, normalized), result);
|
|
@@ -1363,10 +1408,6 @@ class ProjectService {
|
|
|
1363
1408
|
await this.writeGeneratedFile(rootDir, this.resolveManagedPath(rootDir, `${constants_1.DIR_NAMES.DOCS}/${constants_1.DIR_NAMES.DESIGN}/README.md`, defaultLayout), this.templateEngine.generateDesignDocsTemplate(projectName, normalized), result);
|
|
1364
1409
|
await this.writeGeneratedFile(rootDir, this.resolveManagedPath(rootDir, `${constants_1.DIR_NAMES.DOCS}/${constants_1.DIR_NAMES.PLANNING}/README.md`, defaultLayout), this.templateEngine.generatePlanningDocsTemplate(projectName, normalized), result);
|
|
1365
1410
|
await this.writeGeneratedFile(rootDir, this.resolveManagedPath(rootDir, `${constants_1.DIR_NAMES.DOCS}/${constants_1.DIR_NAMES.API}/README.md`, defaultLayout), this.templateEngine.generateApiDocsTemplate(projectName, normalized), result);
|
|
1366
|
-
for (const modulePlan of normalized.modulePlans) {
|
|
1367
|
-
await this.fileService.ensureDir(this.resolveManagedPath(rootDir, `${constants_1.DIR_NAMES.SRC}/${constants_1.DIR_NAMES.MODULES}/${modulePlan.name}`, defaultLayout));
|
|
1368
|
-
await this.writeGeneratedFile(rootDir, this.resolveManagedPath(rootDir, modulePlan.path, defaultLayout), this.templateEngine.generateModuleSkillTemplate(projectName, modulePlan.displayName, normalized, modulePlan.name), result);
|
|
1369
|
-
}
|
|
1370
1411
|
for (const moduleApiPlan of normalized.moduleApiPlans) {
|
|
1371
1412
|
const moduleSlug = moduleApiPlan.name.replace(/^module-/, '');
|
|
1372
1413
|
const modulePlan = normalized.modulePlans.find(plan => plan.name === moduleSlug);
|
|
@@ -1446,9 +1487,6 @@ class ProjectService {
|
|
|
1446
1487
|
constants_1.FILE_NAMES.README,
|
|
1447
1488
|
constants_1.FILE_NAMES.SKILL_MD,
|
|
1448
1489
|
`${constants_1.DIR_NAMES.DOCS}/${constants_1.FILE_NAMES.SKILL_MD}`,
|
|
1449
|
-
`${constants_1.DIR_NAMES.SRC}/${constants_1.FILE_NAMES.SKILL_MD}`,
|
|
1450
|
-
`${constants_1.DIR_NAMES.SRC}/${constants_1.DIR_NAMES.CORE}/${constants_1.FILE_NAMES.SKILL_MD}`,
|
|
1451
|
-
`${constants_1.DIR_NAMES.TESTS}/${constants_1.FILE_NAMES.SKILL_MD}`,
|
|
1452
1490
|
`${constants_1.DIR_NAMES.DOCS}/${constants_1.DIR_NAMES.PROJECT}/overview.md`,
|
|
1453
1491
|
`${constants_1.DIR_NAMES.DOCS}/${constants_1.DIR_NAMES.PROJECT}/tech-stack.md`,
|
|
1454
1492
|
`${constants_1.DIR_NAMES.DOCS}/${constants_1.DIR_NAMES.PROJECT}/architecture.md`,
|
|
@@ -1457,13 +1495,40 @@ class ProjectService {
|
|
|
1457
1495
|
`${constants_1.DIR_NAMES.DOCS}/${constants_1.DIR_NAMES.DESIGN}/README.md`,
|
|
1458
1496
|
`${constants_1.DIR_NAMES.DOCS}/${constants_1.DIR_NAMES.PLANNING}/README.md`,
|
|
1459
1497
|
`${constants_1.DIR_NAMES.DOCS}/${constants_1.DIR_NAMES.API}/README.md`,
|
|
1460
|
-
...normalized.modulePlans.map(plan => plan.path),
|
|
1461
1498
|
...normalized.moduleApiPlans.map(plan => plan.path),
|
|
1462
1499
|
...normalized.apiAreaPlans.map(plan => plan.path),
|
|
1463
1500
|
...normalized.designDocPlans.map(plan => plan.path),
|
|
1464
1501
|
...normalized.planningDocPlans.map(plan => plan.path),
|
|
1465
1502
|
];
|
|
1466
1503
|
}
|
|
1504
|
+
async getExistingOptionalKnowledgeGeneratedPaths(rootDir, config = null) {
|
|
1505
|
+
const paths = new Set();
|
|
1506
|
+
const optionalKnowledgePaths = [
|
|
1507
|
+
`${constants_1.DIR_NAMES.KNOWLEDGE}/${constants_1.DIR_NAMES.SRC}/${constants_1.FILE_NAMES.SKILL_MD}`,
|
|
1508
|
+
`${constants_1.DIR_NAMES.KNOWLEDGE}/${constants_1.DIR_NAMES.SRC}/${constants_1.DIR_NAMES.CORE}/${constants_1.FILE_NAMES.SKILL_MD}`,
|
|
1509
|
+
`${constants_1.DIR_NAMES.KNOWLEDGE}/${constants_1.DIR_NAMES.TESTS}/${constants_1.FILE_NAMES.SKILL_MD}`,
|
|
1510
|
+
`${constants_1.DIR_NAMES.SRC}/${constants_1.FILE_NAMES.SKILL_MD}`,
|
|
1511
|
+
`${constants_1.DIR_NAMES.SRC}/${constants_1.DIR_NAMES.CORE}/${constants_1.FILE_NAMES.SKILL_MD}`,
|
|
1512
|
+
`${constants_1.DIR_NAMES.TESTS}/${constants_1.FILE_NAMES.SKILL_MD}`,
|
|
1513
|
+
];
|
|
1514
|
+
for (const relativePath of optionalKnowledgePaths) {
|
|
1515
|
+
const absolutePath = this.resolveManagedPath(rootDir, relativePath, config);
|
|
1516
|
+
if (await this.fileService.exists(absolutePath)) {
|
|
1517
|
+
paths.add(relativePath);
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
const modules = await this.scanModules(rootDir);
|
|
1521
|
+
for (const module of modules) {
|
|
1522
|
+
const relativePath = this.toRelativePath(rootDir, module.skillPath)
|
|
1523
|
+
.replace(/^\.ospec\//, '')
|
|
1524
|
+
.replace(/\\/g, '/');
|
|
1525
|
+
if (relativePath.startsWith(`${constants_1.DIR_NAMES.KNOWLEDGE}/${constants_1.DIR_NAMES.SRC}/${constants_1.DIR_NAMES.MODULES}/`) ||
|
|
1526
|
+
relativePath.startsWith(`${constants_1.DIR_NAMES.SRC}/${constants_1.DIR_NAMES.MODULES}/`)) {
|
|
1527
|
+
paths.add(relativePath);
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
return Array.from(paths).sort((left, right) => left.localeCompare(right));
|
|
1531
|
+
}
|
|
1467
1532
|
getBootstrapAssetPlan(documentLanguage, normalized, config = null) {
|
|
1468
1533
|
const staticPlan = this.projectAssetService.getAssetPlan(documentLanguage, this.getProjectLayout(config || { projectLayout: 'nested' }));
|
|
1469
1534
|
return {
|
|
@@ -26,8 +26,7 @@ ${context.summary}
|
|
|
26
26
|
|
|
27
27
|
- 根技能文档:\`SKILL.md\`
|
|
28
28
|
- 文档中心:\`docs/SKILL.md\`
|
|
29
|
-
-
|
|
30
|
-
- 测试说明:\`tests/SKILL.md\`
|
|
29
|
+
- 可选知识地图:\`knowledge/src/SKILL.md\`、\`knowledge/tests/SKILL.md\`
|
|
31
30
|
- 语义索引:\`SKILL.index.json\`
|
|
32
31
|
|
|
33
32
|
## 执行层
|
|
@@ -49,8 +48,7 @@ ${context.summary}
|
|
|
49
48
|
|
|
50
49
|
- Root skill: \`SKILL.md\`
|
|
51
50
|
- Docs hub: \`docs/SKILL.md\`
|
|
52
|
-
-
|
|
53
|
-
- Test guide: \`tests/SKILL.md\`
|
|
51
|
+
- Optional knowledge maps: \`knowledge/src/SKILL.md\`, \`knowledge/tests/SKILL.md\`
|
|
54
52
|
- Index: \`SKILL.index.json\`
|
|
55
53
|
|
|
56
54
|
## Execution
|
|
@@ -72,8 +70,7 @@ ${context.summary}
|
|
|
72
70
|
|
|
73
71
|
- ルート SKILL: \`SKILL.md\`
|
|
74
72
|
- docs ハブ: \`docs/SKILL.md\`
|
|
75
|
-
-
|
|
76
|
-
- テストガイド: \`tests/SKILL.md\`
|
|
73
|
+
- 任意の知識マップ: \`knowledge/src/SKILL.md\`、\`knowledge/tests/SKILL.md\`
|
|
77
74
|
- インデックス: \`SKILL.index.json\`
|
|
78
75
|
|
|
79
76
|
## 実行レイヤー
|
|
@@ -95,8 +92,7 @@ ${context.summary}
|
|
|
95
92
|
|
|
96
93
|
- ملف SKILL الجذري: \`SKILL.md\`
|
|
97
94
|
- مركز docs: \`docs/SKILL.md\`
|
|
98
|
-
-
|
|
99
|
-
- دليل الاختبارات: \`tests/SKILL.md\`
|
|
95
|
+
- خرائط معرفة اختيارية: \`knowledge/src/SKILL.md\` و \`knowledge/tests/SKILL.md\`
|
|
100
96
|
- الفهرس: \`SKILL.index.json\`
|
|
101
97
|
|
|
102
98
|
## طبقة التنفيذ
|
|
@@ -128,9 +124,8 @@ ${context.architecture}
|
|
|
128
124
|
## 目录导航
|
|
129
125
|
|
|
130
126
|
- 文档中心:[docs/SKILL.md](docs/SKILL.md)
|
|
131
|
-
- 源码地图:[src/SKILL.md](src/SKILL.md)
|
|
132
|
-
- 测试入口:[tests/SKILL.md](tests/SKILL.md)
|
|
133
127
|
- AI 指南:[for-ai/ai-guide.md](for-ai/ai-guide.md)
|
|
128
|
+
- 可选知识地图:\`knowledge/src/SKILL.md\`、\`knowledge/tests/SKILL.md\`
|
|
134
129
|
|
|
135
130
|
## 插件阻断
|
|
136
131
|
|
|
@@ -158,9 +153,8 @@ ${context.architecture}
|
|
|
158
153
|
## Navigation
|
|
159
154
|
|
|
160
155
|
- Docs hub: [docs/SKILL.md](docs/SKILL.md)
|
|
161
|
-
- Source map: [src/SKILL.md](src/SKILL.md)
|
|
162
|
-
- Test guide: [tests/SKILL.md](tests/SKILL.md)
|
|
163
156
|
- AI guide: [for-ai/ai-guide.md](for-ai/ai-guide.md)
|
|
157
|
+
- Optional knowledge maps: \`knowledge/src/SKILL.md\`, \`knowledge/tests/SKILL.md\`
|
|
164
158
|
|
|
165
159
|
## Plugin Gates
|
|
166
160
|
|
|
@@ -188,9 +182,8 @@ ${context.architecture}
|
|
|
188
182
|
## ナビゲーション
|
|
189
183
|
|
|
190
184
|
- docs ハブ: [docs/SKILL.md](docs/SKILL.md)
|
|
191
|
-
- ソースマップ: [src/SKILL.md](src/SKILL.md)
|
|
192
|
-
- テストガイド: [tests/SKILL.md](tests/SKILL.md)
|
|
193
185
|
- AI ガイド: [for-ai/ai-guide.md](for-ai/ai-guide.md)
|
|
186
|
+
- 任意の知識マップ: \`knowledge/src/SKILL.md\`、\`knowledge/tests/SKILL.md\`
|
|
194
187
|
|
|
195
188
|
## プラグインゲート
|
|
196
189
|
|
|
@@ -218,9 +211,8 @@ ${context.architecture}
|
|
|
218
211
|
## التنقل
|
|
219
212
|
|
|
220
213
|
- مركز docs: [docs/SKILL.md](docs/SKILL.md)
|
|
221
|
-
- خريطة المصدر: [src/SKILL.md](src/SKILL.md)
|
|
222
|
-
- دليل الاختبارات: [tests/SKILL.md](tests/SKILL.md)
|
|
223
214
|
- دليل الذكاء الاصطناعي: [for-ai/ai-guide.md](for-ai/ai-guide.md)
|
|
215
|
+
- خرائط معرفة اختيارية: \`knowledge/src/SKILL.md\` و \`knowledge/tests/SKILL.md\`
|
|
224
216
|
|
|
225
217
|
## بوابات الإضافات
|
|
226
218
|
|
|
@@ -298,7 +290,9 @@ ${context.architecture}
|
|
|
298
290
|
const context = this.getProjectContext(fallbackName, 'standard', input);
|
|
299
291
|
const moduleLinks = this.formatLinkedList(context.modulePlans.map(plan => ({
|
|
300
292
|
displayName: plan.displayName,
|
|
301
|
-
path: plan.path
|
|
293
|
+
path: plan.path
|
|
294
|
+
.replace(`${constants_1.DIR_NAMES.KNOWLEDGE}/${constants_1.DIR_NAMES.SRC}/`, '')
|
|
295
|
+
.replace(`${constants_1.DIR_NAMES.SRC}/`, ''),
|
|
302
296
|
})), this.copy(context.documentLanguage, '待补充', 'TBD', '未定', 'قيد التحديد'));
|
|
303
297
|
const body = this.copy(context.documentLanguage, `# 源码地图
|
|
304
298
|
|
|
@@ -307,7 +301,7 @@ ${context.architecture}
|
|
|
307
301
|
## 目录导航
|
|
308
302
|
|
|
309
303
|
- 核心层:[core/SKILL.md](core/SKILL.md)
|
|
310
|
-
- 模块层:\`src/modules/<module>/SKILL.md\`
|
|
304
|
+
- 模块层:\`knowledge/src/modules/<module>/SKILL.md\`
|
|
311
305
|
|
|
312
306
|
## 模块说明
|
|
313
307
|
|
|
@@ -318,7 +312,7 @@ ${moduleLinks}`, `# Source Map
|
|
|
318
312
|
## Navigation
|
|
319
313
|
|
|
320
314
|
- Core layer: [core/SKILL.md](core/SKILL.md)
|
|
321
|
-
- Module layer: \`src/modules/<module>/SKILL.md\`
|
|
315
|
+
- Module layer: \`knowledge/src/modules/<module>/SKILL.md\`
|
|
322
316
|
|
|
323
317
|
## Modules
|
|
324
318
|
|
|
@@ -329,7 +323,7 @@ ${moduleLinks}`, `# ソースマップ
|
|
|
329
323
|
## ナビゲーション
|
|
330
324
|
|
|
331
325
|
- コア層: [core/SKILL.md](core/SKILL.md)
|
|
332
|
-
- モジュール層: \`src/modules/<module>/SKILL.md\`
|
|
326
|
+
- モジュール層: \`knowledge/src/modules/<module>/SKILL.md\`
|
|
333
327
|
|
|
334
328
|
## モジュール
|
|
335
329
|
|
|
@@ -340,7 +334,7 @@ ${moduleLinks}`, `# خريطة المصدر
|
|
|
340
334
|
## التنقل
|
|
341
335
|
|
|
342
336
|
- الطبقة الأساسية: [core/SKILL.md](core/SKILL.md)
|
|
343
|
-
- طبقة الوحدات: \`src/modules/<module>/SKILL.md\`
|
|
337
|
+
- طبقة الوحدات: \`knowledge/src/modules/<module>/SKILL.md\`
|
|
344
338
|
|
|
345
339
|
## الوحدات
|
|
346
340
|
|
|
@@ -356,7 +350,7 @@ ${moduleLinks}`);
|
|
|
356
350
|
const body = this.copy(context.documentLanguage, `# Core 核心层
|
|
357
351
|
|
|
358
352
|
> 层级:第 3 层(核心模块)
|
|
359
|
-
> 上层:[src/SKILL.md](../SKILL.md)
|
|
353
|
+
> 上层:[knowledge/src/SKILL.md](../SKILL.md)
|
|
360
354
|
|
|
361
355
|
## 模块概述
|
|
362
356
|
|
|
@@ -366,10 +360,10 @@ ${moduleLinks}`);
|
|
|
366
360
|
|
|
367
361
|
## API 文档
|
|
368
362
|
|
|
369
|
-
- 项目 API 总览:[
|
|
363
|
+
- 项目 API 总览:[../../../docs/project/api-overview.md](../../../docs/project/api-overview.md)`, `# Core Layer
|
|
370
364
|
|
|
371
365
|
> Layer: core module
|
|
372
|
-
> Parent: [src/SKILL.md](../SKILL.md)
|
|
366
|
+
> Parent: [knowledge/src/SKILL.md](../SKILL.md)
|
|
373
367
|
|
|
374
368
|
## Module Overview
|
|
375
369
|
|
|
@@ -379,10 +373,10 @@ ${moduleLinks}`);
|
|
|
379
373
|
|
|
380
374
|
## API Docs
|
|
381
375
|
|
|
382
|
-
- Project API overview: [
|
|
376
|
+
- Project API overview: [../../../docs/project/api-overview.md](../../../docs/project/api-overview.md)`, `# コア層
|
|
383
377
|
|
|
384
378
|
> レイヤー: コアモジュール
|
|
385
|
-
> 親: [src/SKILL.md](../SKILL.md)
|
|
379
|
+
> 親: [knowledge/src/SKILL.md](../SKILL.md)
|
|
386
380
|
|
|
387
381
|
## モジュール概要
|
|
388
382
|
|
|
@@ -392,10 +386,10 @@ ${moduleLinks}`);
|
|
|
392
386
|
|
|
393
387
|
## API 文書
|
|
394
388
|
|
|
395
|
-
- プロジェクト API 概要: [
|
|
389
|
+
- プロジェクト API 概要: [../../../docs/project/api-overview.md](../../../docs/project/api-overview.md)`, `# الطبقة الأساسية
|
|
396
390
|
|
|
397
391
|
> الطبقة: الوحدة الأساسية
|
|
398
|
-
> الأصل: [src/SKILL.md](../SKILL.md)
|
|
392
|
+
> الأصل: [knowledge/src/SKILL.md](../SKILL.md)
|
|
399
393
|
|
|
400
394
|
## نظرة عامة على الوحدة
|
|
401
395
|
|
|
@@ -405,7 +399,7 @@ ${moduleLinks}`);
|
|
|
405
399
|
|
|
406
400
|
## وثائق API
|
|
407
401
|
|
|
408
|
-
- نظرة عامة على API للمشروع: [
|
|
402
|
+
- نظرة عامة على API للمشروع: [../../../docs/project/api-overview.md](../../../docs/project/api-overview.md)`);
|
|
409
403
|
return this.withFrontmatter({
|
|
410
404
|
name: 'core',
|
|
411
405
|
title: this.copy(context.documentLanguage, `${context.projectName} 核心层`, `${context.projectName} Core Layer`, `${context.projectName} コア層`, `${context.projectName} الطبقة الأساسية`),
|
|
@@ -878,7 +872,7 @@ ${apiDocs}`);
|
|
|
878
872
|
const body = presetBody ?? this.copy(context.documentLanguage, `# ${moduleName} 模块
|
|
879
873
|
|
|
880
874
|
> 层级:第 3 层(业务模块文档)
|
|
881
|
-
> 上层:[src/SKILL.md](../../SKILL.md)
|
|
875
|
+
> 上层:[knowledge/src/SKILL.md](../../SKILL.md)
|
|
882
876
|
|
|
883
877
|
## 模块概述
|
|
884
878
|
|
|
@@ -910,12 +904,12 @@ ${this.formatReferenceList(refs, '待补充')}
|
|
|
910
904
|
|
|
911
905
|
## 关联文档
|
|
912
906
|
|
|
913
|
-
- 项目模块地图:[
|
|
914
|
-
- API 总览:[
|
|
907
|
+
- 项目模块地图:[../../../../docs/project/module-map.md](../../../../docs/project/module-map.md)
|
|
908
|
+
- API 总览:[../../../../docs/project/api-overview.md](../../../../docs/project/api-overview.md)
|
|
915
909
|
- 模块源码入口:当前目录`, `# ${moduleName}
|
|
916
910
|
|
|
917
911
|
> Layer: module document
|
|
918
|
-
> Parent: [src/SKILL.md](../../SKILL.md)
|
|
912
|
+
> Parent: [knowledge/src/SKILL.md](../../SKILL.md)
|
|
919
913
|
|
|
920
914
|
## Module Overview
|
|
921
915
|
|
|
@@ -947,11 +941,11 @@ ${this.formatReferenceList(refs, 'TBD')}
|
|
|
947
941
|
|
|
948
942
|
## Related Docs
|
|
949
943
|
|
|
950
|
-
- Module map: [
|
|
951
|
-
- API overview: [
|
|
944
|
+
- Module map: [../../../../docs/project/module-map.md](../../../../docs/project/module-map.md)
|
|
945
|
+
- API overview: [../../../../docs/project/api-overview.md](../../../../docs/project/api-overview.md)`, `# ${moduleName} モジュール
|
|
952
946
|
|
|
953
947
|
> レイヤー: モジュール文書
|
|
954
|
-
> 親: [src/SKILL.md](../../SKILL.md)
|
|
948
|
+
> 親: [knowledge/src/SKILL.md](../../SKILL.md)
|
|
955
949
|
|
|
956
950
|
## モジュール概要
|
|
957
951
|
|
|
@@ -983,11 +977,11 @@ ${this.formatReferenceList(refs, '未定')}
|
|
|
983
977
|
|
|
984
978
|
## 関連文書
|
|
985
979
|
|
|
986
|
-
- モジュールマップ: [
|
|
987
|
-
- API 概要: [
|
|
980
|
+
- モジュールマップ: [../../../../docs/project/module-map.md](../../../../docs/project/module-map.md)
|
|
981
|
+
- API 概要: [../../../../docs/project/api-overview.md](../../../../docs/project/api-overview.md)`, `# وحدة ${moduleName}
|
|
988
982
|
|
|
989
983
|
> الطبقة: وثيقة الوحدة
|
|
990
|
-
> الأصل: [src/SKILL.md](../../SKILL.md)
|
|
984
|
+
> الأصل: [knowledge/src/SKILL.md](../../SKILL.md)
|
|
991
985
|
|
|
992
986
|
## نظرة عامة على الوحدة
|
|
993
987
|
|
|
@@ -1019,8 +1013,8 @@ ${this.formatReferenceList(refs, 'قيد التحديد')}
|
|
|
1019
1013
|
|
|
1020
1014
|
## وثائق ذات صلة
|
|
1021
1015
|
|
|
1022
|
-
- خريطة الوحدات: [
|
|
1023
|
-
- نظرة عامة على API: [
|
|
1016
|
+
- خريطة الوحدات: [../../../../docs/project/module-map.md](../../../../docs/project/module-map.md)
|
|
1017
|
+
- نظرة عامة على API: [../../../../docs/project/api-overview.md](../../../../docs/project/api-overview.md)`);
|
|
1024
1018
|
return this.withFrontmatter({
|
|
1025
1019
|
name: slug,
|
|
1026
1020
|
title: this.copy(context.documentLanguage, `${context.projectName} ${moduleName} 模块`, `${context.projectName} ${moduleName} Module`, `${context.projectName} ${moduleName} モジュール`, `${context.projectName} وحدة ${moduleName}`),
|
|
@@ -1601,16 +1595,16 @@ scan(rootDir)
|
|
|
1601
1595
|
const moduleKey = slug.toLowerCase();
|
|
1602
1596
|
const docsLink = this.formatReferenceList(refs, this.copy(context.documentLanguage, '待补充', 'TBD'));
|
|
1603
1597
|
const relatedDocs = this.isEnglish(context.documentLanguage)
|
|
1604
|
-
? `- Module map: [
|
|
1605
|
-
- API overview: [
|
|
1606
|
-
: `- 项目模块地图:[
|
|
1607
|
-
- API 总览:[
|
|
1598
|
+
? `- Module map: [../../../../docs/project/module-map.md](../../../../docs/project/module-map.md)
|
|
1599
|
+
- API overview: [../../../../docs/project/api-overview.md](../../../../docs/project/api-overview.md)`
|
|
1600
|
+
: `- 项目模块地图:[../../../../docs/project/module-map.md](../../../../docs/project/module-map.md)
|
|
1601
|
+
- API 总览:[../../../../docs/project/api-overview.md](../../../../docs/project/api-overview.md)`;
|
|
1608
1602
|
const templates = {
|
|
1609
1603
|
web: {
|
|
1610
1604
|
zh: `# ${moduleName} 模块
|
|
1611
1605
|
|
|
1612
1606
|
> 层级:第 3 层(业务模块文档)
|
|
1613
|
-
> 上层:[src/SKILL.md](../../SKILL.md)
|
|
1607
|
+
> 上层:[knowledge/src/SKILL.md](../../SKILL.md)
|
|
1614
1608
|
|
|
1615
1609
|
## 模块定位
|
|
1616
1610
|
|
|
@@ -1646,7 +1640,7 @@ ${relatedDocs}`,
|
|
|
1646
1640
|
en: `# ${moduleName}
|
|
1647
1641
|
|
|
1648
1642
|
> Layer: module document
|
|
1649
|
-
> Parent: [src/SKILL.md](../../SKILL.md)
|
|
1643
|
+
> Parent: [knowledge/src/SKILL.md](../../SKILL.md)
|
|
1650
1644
|
|
|
1651
1645
|
## Module Positioning
|
|
1652
1646
|
|
|
@@ -1684,7 +1678,7 @@ ${relatedDocs}`,
|
|
|
1684
1678
|
zh: `# ${moduleName} 模块
|
|
1685
1679
|
|
|
1686
1680
|
> 层级:第 3 层(业务模块文档)
|
|
1687
|
-
> 上层:[src/SKILL.md](../../SKILL.md)
|
|
1681
|
+
> 上层:[knowledge/src/SKILL.md](../../SKILL.md)
|
|
1688
1682
|
|
|
1689
1683
|
## 模块定位
|
|
1690
1684
|
|
|
@@ -1720,7 +1714,7 @@ ${relatedDocs}`,
|
|
|
1720
1714
|
en: `# ${moduleName}
|
|
1721
1715
|
|
|
1722
1716
|
> Layer: module document
|
|
1723
|
-
> Parent: [src/SKILL.md](../../SKILL.md)
|
|
1717
|
+
> Parent: [knowledge/src/SKILL.md](../../SKILL.md)
|
|
1724
1718
|
|
|
1725
1719
|
## Module Positioning
|
|
1726
1720
|
|
|
@@ -1758,7 +1752,7 @@ ${relatedDocs}`,
|
|
|
1758
1752
|
zh: `# ${moduleName} 模块
|
|
1759
1753
|
|
|
1760
1754
|
> 层级:第 3 层(业务模块文档)
|
|
1761
|
-
> 上层:[src/SKILL.md](../../SKILL.md)
|
|
1755
|
+
> 上层:[knowledge/src/SKILL.md](../../SKILL.md)
|
|
1762
1756
|
|
|
1763
1757
|
## 模块定位
|
|
1764
1758
|
|
|
@@ -1794,7 +1788,7 @@ ${relatedDocs}`,
|
|
|
1794
1788
|
en: `# ${moduleName}
|
|
1795
1789
|
|
|
1796
1790
|
> Layer: module document
|
|
1797
|
-
> Parent: [src/SKILL.md](../../SKILL.md)
|
|
1791
|
+
> Parent: [knowledge/src/SKILL.md](../../SKILL.md)
|
|
1798
1792
|
|
|
1799
1793
|
## Module Positioning
|
|
1800
1794
|
|
|
@@ -1832,7 +1826,7 @@ ${relatedDocs}`,
|
|
|
1832
1826
|
zh: `# ${moduleName} 模块
|
|
1833
1827
|
|
|
1834
1828
|
> 层级:第 3 层(业务模块文档)
|
|
1835
|
-
> 上层:[src/SKILL.md](../../SKILL.md)
|
|
1829
|
+
> 上层:[knowledge/src/SKILL.md](../../SKILL.md)
|
|
1836
1830
|
|
|
1837
1831
|
## 模块定位
|
|
1838
1832
|
|
|
@@ -1868,7 +1862,7 @@ ${relatedDocs}`,
|
|
|
1868
1862
|
en: `# ${moduleName}
|
|
1869
1863
|
|
|
1870
1864
|
> Layer: module document
|
|
1871
|
-
> Parent: [src/SKILL.md](../../SKILL.md)
|
|
1865
|
+
> Parent: [knowledge/src/SKILL.md](../../SKILL.md)
|
|
1872
1866
|
|
|
1873
1867
|
## Module Positioning
|
|
1874
1868
|
|
|
@@ -1906,7 +1900,7 @@ ${relatedDocs}`,
|
|
|
1906
1900
|
zh: `# ${moduleName} 模块
|
|
1907
1901
|
|
|
1908
1902
|
> 层级:第 3 层(业务模块文档)
|
|
1909
|
-
> 上层:[src/SKILL.md](../../SKILL.md)
|
|
1903
|
+
> 上层:[knowledge/src/SKILL.md](../../SKILL.md)
|
|
1910
1904
|
|
|
1911
1905
|
## 模块定位
|
|
1912
1906
|
|
|
@@ -1942,7 +1936,7 @@ ${relatedDocs}`,
|
|
|
1942
1936
|
en: `# ${moduleName}
|
|
1943
1937
|
|
|
1944
1938
|
> Layer: module document
|
|
1945
|
-
> Parent: [src/SKILL.md](../../SKILL.md)
|
|
1939
|
+
> Parent: [knowledge/src/SKILL.md](../../SKILL.md)
|
|
1946
1940
|
|
|
1947
1941
|
## Module Positioning
|
|
1948
1942
|
|
|
@@ -179,7 +179,7 @@ class TemplateInputFactory {
|
|
|
179
179
|
const inferredFields = this.pickFieldKeys(fieldSources, 'inferred');
|
|
180
180
|
const placeholderFields = this.pickFieldKeys(fieldSources, 'placeholder');
|
|
181
181
|
const usedFallbacks = [...inferredFields, ...placeholderFields];
|
|
182
|
-
const modulePlans = this.buildPlannedFiles(modules, 'module', value => this.normalizeModuleDisplayName(value), slug => `${constants_1.DIR_NAMES.SRC}/${constants_1.DIR_NAMES.MODULES}/${slug}/${constants_1.FILE_NAMES.SKILL_MD}`, displayName => displayName.toLowerCase() === 'core');
|
|
182
|
+
const modulePlans = this.buildPlannedFiles(modules, 'module', value => this.normalizeModuleDisplayName(value), slug => `${constants_1.DIR_NAMES.KNOWLEDGE}/${constants_1.DIR_NAMES.SRC}/${constants_1.DIR_NAMES.MODULES}/${slug}/${constants_1.FILE_NAMES.SKILL_MD}`, displayName => displayName.toLowerCase() === 'core');
|
|
183
183
|
const moduleApiPlans = this.buildModuleApiPlans(modulePlans);
|
|
184
184
|
const apiAreaPlans = this.buildPlannedFiles(apiAreas, 'api', value => this.normalizeDocDisplayName(value, constants_1.DIR_NAMES.API), slug => `${constants_1.DIR_NAMES.DOCS}/${constants_1.DIR_NAMES.API}/${slug}.md`);
|
|
185
185
|
const designDocPlans = this.buildPlannedFiles(designDocs, 'design', value => this.normalizeDocDisplayName(value, constants_1.DIR_NAMES.DESIGN), slug => `${constants_1.DIR_NAMES.DOCS}/${constants_1.DIR_NAMES.DESIGN}/${slug}.md`);
|
|
@@ -314,6 +314,7 @@ class TemplateInputFactory {
|
|
|
314
314
|
}
|
|
315
315
|
normalizeModuleDisplayName(value) {
|
|
316
316
|
return value
|
|
317
|
+
.replace(/^knowledge[\\/]+src[\\/]+modules[\\/]+/i, '')
|
|
317
318
|
.replace(/^src[\\/]+modules[\\/]+/i, '')
|
|
318
319
|
.replace(/^modules[\\/]+/i, '')
|
|
319
320
|
.replace(/[\\/]+/g, ' ')
|
|
@@ -91,6 +91,7 @@ function resolveManagedInputPath(rootDir, candidatePath, input) {
|
|
|
91
91
|
const normalizedCandidatePath = String(candidatePath || '').replace(/\\/g, '/').replace(/^\.\/+/, '');
|
|
92
92
|
if (normalizedCandidatePath.startsWith('changes/') ||
|
|
93
93
|
normalizedCandidatePath.startsWith('for-ai/') ||
|
|
94
|
+
normalizedCandidatePath.startsWith('knowledge/') ||
|
|
94
95
|
normalizedCandidatePath.startsWith('docs/') ||
|
|
95
96
|
normalizedCandidatePath.startsWith('src/') ||
|
|
96
97
|
normalizedCandidatePath.startsWith('tests/') ||
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clawplays/ospec-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Official OSpec CLI package for spec-driven development (SDD) and document-driven development in AI coding agent and CLI workflows.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
package/skill.yaml
CHANGED
|
@@ -7,7 +7,7 @@ license: MIT
|
|
|
7
7
|
interface:
|
|
8
8
|
display_name: "OSpec"
|
|
9
9
|
short_description: "Inspect, initialize, and operate OSpec projects"
|
|
10
|
-
default_prompt: "Use $ospec to initialize this directory according to OSpec rules: use ospec init so the repository ends in change-ready state, reuse existing project docs when available, ask one concise follow-up for missing summary or tech stack in AI-assisted flows, fall back to placeholder docs when the user skips that context, do not assume a web template when the project type is unclear, and do not create the first change automatically."
|
|
10
|
+
default_prompt: "Use $ospec to initialize this directory according to OSpec rules: use ospec init so the repository ends in change-ready state, reuse existing project docs when available, map an explicit language request or the current conversation language to --document-language during AI-assisted init instead of assuming a brand-new repo will infer it, ask one concise follow-up for missing summary or tech stack in AI-assisted flows, fall back to placeholder docs when the user skips that context, do not assume a web template when the project type is unclear, and do not create the first change automatically."
|
|
11
11
|
|
|
12
12
|
requires_local:
|
|
13
13
|
node: ">=18.0.0"
|