@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 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. 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
41
- 3. verify the actual filesystem result before claiming initialization is complete
42
- 4. stop before `ospec new` unless the user explicitly asks to create a change
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`, `tests/SKILL.md`, or business scaffold as if they were part of change-ready completion.
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]
@@ -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.1';
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);
@@ -19,6 +19,7 @@ export declare const DIR_NAMES: {
19
19
  CHANGES: string;
20
20
  ACTIVE: string;
21
21
  ARCHIVED: string;
22
+ KNOWLEDGE: string;
22
23
  FOR_AI: string;
23
24
  DOCS: string;
24
25
  PROJECT: string;
@@ -42,6 +42,7 @@ exports.DIR_NAMES = {
42
42
  RUNS: 'runs',
43
43
  HISTORY: 'history',
44
44
  LOGS: 'logs',
45
+ KNOWLEDGE: 'knowledge',
45
46
  FOR_AI: 'for-ai',
46
47
  DOCS: 'docs',
47
48
  PROJECT: 'project',
@@ -101,6 +101,7 @@ export interface ArchiveConfig {
101
101
  export interface SkillrcConfig {
102
102
  version: string;
103
103
  mode: ProjectMode;
104
+ ospecCliVersion?: string;
104
105
  projectLayout?: ProjectLayout;
105
106
  documentLanguage?: 'en-US' | 'zh-CN' | 'ja-JP' | 'ar';
106
107
  hooks: {
@@ -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: this.getFullBootstrapTemplateGeneratedPaths(normalized),
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
- templateGeneratedPaths: this.getFullBootstrapTemplateGeneratedPaths(normalized),
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
- templateGeneratedPaths: assetPlan.templateGeneratedFiles,
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 modulesDir = this.resolveManagedPath(rootDir, `${constants_1.DIR_NAMES.SRC}/${constants_1.DIR_NAMES.MODULES}`, config);
426
- if (!(await this.fileService.exists(modulesDir))) {
427
- return [];
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
- const entries = await fs_1.promises.readdir(modulesDir, { withFileTypes: true });
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: normalized.modulePlans.map(plan => plan.path),
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
- - 源码地图:\`src/SKILL.md\`
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
- - Source map: \`src/SKILL.md\`
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
- - ソースマップ: \`src/SKILL.md\`
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
- - خريطة المصدر: \`src/SKILL.md\`
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.replace(`${constants_1.DIR_NAMES.SRC}/`, ''),
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 总览:[../../docs/project/api-overview.md](../../docs/project/api-overview.md)`, `# Core Layer
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: [../../docs/project/api-overview.md](../../docs/project/api-overview.md)`, `# コア層
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 概要: [../../docs/project/api-overview.md](../../docs/project/api-overview.md)`, `# الطبقة الأساسية
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 للمشروع: [../../docs/project/api-overview.md](../../docs/project/api-overview.md)`);
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
- - 项目模块地图:[../../../docs/project/module-map.md](../../../docs/project/module-map.md)
914
- - API 总览:[../../../docs/project/api-overview.md](../../../docs/project/api-overview.md)
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: [../../../docs/project/module-map.md](../../../docs/project/module-map.md)
951
- - API overview: [../../../docs/project/api-overview.md](../../../docs/project/api-overview.md)`, `# ${moduleName} モジュール
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
- - モジュールマップ: [../../../docs/project/module-map.md](../../../docs/project/module-map.md)
987
- - API 概要: [../../../docs/project/api-overview.md](../../../docs/project/api-overview.md)`, `# وحدة ${moduleName}
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
- - خريطة الوحدات: [../../../docs/project/module-map.md](../../../docs/project/module-map.md)
1023
- - نظرة عامة على API: [../../../docs/project/api-overview.md](../../../docs/project/api-overview.md)`);
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: [../../../docs/project/module-map.md](../../../docs/project/module-map.md)
1605
- - API overview: [../../../docs/project/api-overview.md](../../../docs/project/api-overview.md)`
1606
- : `- 项目模块地图:[../../../docs/project/module-map.md](../../../docs/project/module-map.md)
1607
- - API 总览:[../../../docs/project/api-overview.md](../../../docs/project/api-overview.md)`;
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.1",
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"