Skip to content

Commit 3468bfd

Browse files
committed
Release 1.6.0
1 parent c1f08ff commit 3468bfd

19 files changed

Lines changed: 248 additions & 181 deletions

dist/cli.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ const VerifyCommand_1 = require("./commands/VerifyCommand");
6262
const WorkflowCommand_1 = require("./commands/WorkflowCommand");
6363
const LayoutCommand_1 = require("./commands/LayoutCommand");
6464
const services_1 = require("./services");
65-
const CLI_VERSION = '1.5.1';
65+
const CLI_VERSION = '1.6.0';
6666
function showInitUsage() {
6767
console.log('Usage: ospec init [root-dir] [--summary "..."] [--tech-stack node,react] [--architecture "..."] [--document-language en-US|zh-CN|ja-JP|ar]');
6868
}

dist/commands/UpdateCommand.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ class UpdateCommand extends BaseCommand_1.BaseCommand {
3030
const toolingResult = await this.syncProjectTooling(targetPath, protocolResult.documentLanguage);
3131
const pluginResult = await this.syncEnabledPluginAssets(targetPath);
3232
const archiveResult = await this.syncArchiveLayout(targetPath);
33+
// Regenerate SKILL.index.json with the refreshed tooling so existing changes and
34+
// newly-indexed knowledge docs are reflected for subsequent feature work. Never let
35+
// an index rebuild failure abort the rest of the update.
36+
const indexResult = await services_1.services.projectService.rebuildIndex(targetPath, { syncAssets: false }).catch(() => null);
3337
const skillResult = await this.syncInstalledSkills();
3438
const postSyncMaintenance = await this.runPostSyncMaintenance();
3539
const refreshedFiles = Array.from(new Set([
@@ -70,6 +74,9 @@ class UpdateCommand extends BaseCommand_1.BaseCommand {
7074
this.info(` legacy knowledge paths removed: ${legacyKnowledgeMigration.removedPaths.join(', ')}`);
7175
}
7276
}
77+
if (indexResult?.stats) {
78+
this.info(` index regenerated: ${indexResult.stats.totalModules} modules, ${indexResult.stats.totalSections} sections`);
79+
}
7380
if (toolingResult.hookInstalledFiles.length > 0) {
7481
this.info(` git hooks refreshed: ${toolingResult.hookInstalledFiles.join(', ')}`);
7582
}
@@ -113,7 +120,7 @@ class UpdateCommand extends BaseCommand_1.BaseCommand {
113120
if (archiveResult.migratedChanges.length > 0) {
114121
this.info(` archived changes migrated: ${archiveResult.migratedChanges.length}`);
115122
}
116-
this.info(' note: update refreshes protocol docs, tooling, hooks, managed skills, managed assets for already-enabled plugins, and the archive layout when needed');
123+
this.info(' note: update refreshes protocol docs, tooling, hooks, managed skills, managed assets for already-enabled plugins, the archive layout when needed, and regenerates SKILL.index.json');
117124
this.info(' note: it can repair legacy OSpec projects with an existing OSpec footprint before refreshing assets');
118125
this.info(' note: it auto-upgrades already-enabled plugin npm packages only when a newer compatible version is available');
119126
this.info(' note: it does not upgrade the CLI itself');

dist/commands/VerifyCommand.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ class VerifyCommand extends BaseCommand_1.BaseCommand {
4646
? (0, ProjectLayout_1.resolveManagedInputPath)(process.cwd(), featurePath, await services_1.services.configManager.loadConfig(process.cwd()).catch(() => null))
4747
: featurePath || process.cwd();
4848
this.logger.info(`Verifying change at ${targetPath}`);
49+
// Keep the index fresh for the verification context. Idempotent (a no-op when the
50+
// index is already current) and best-effort — never block verification on it.
51+
await services_1.services.projectService.rebuildIndexForPath(targetPath, { syncAssets: false }).catch(() => null);
4952
const outcome = await (0, VerificationService_1.createVerificationService)().verify(targetPath);
5053
console.log('\nChange Verification Results:');
5154
console.log('====================\n');

dist/core/types.d.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,6 @@ export interface SkillFrontmatter {
167167
export interface SkillSection {
168168
level: number;
169169
title: string;
170-
start: number;
171-
end: number;
172170
tags?: string[];
173171
}
174172
export interface IndexModule {

dist/services/IndexBuilder.d.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
import { SkillIndex } from '../core/types';
22
import { SkillParser } from './SkillParser';
33
export declare class IndexBuilder {
4-
private skillParser;
5-
constructor(skillParser: SkillParser);
4+
constructor(_skillParser?: SkillParser);
65
build(rootDir: string): Promise<SkillIndex>;
76
write(rootDir: string): Promise<SkillIndex>;
87
createEmpty(rootDir: string): Promise<SkillIndex>;
9-
private stripVolatileFields;
10-
private readProjectConfig;
118
}
12-
export declare const createIndexBuilder: (skillParser: SkillParser) => IndexBuilder;
9+
export declare const createIndexBuilder: (skillParser?: SkillParser) => IndexBuilder;

dist/services/IndexBuilder.js

Lines changed: 21 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
"use strict";
2-
var __importDefault = (this && this.__importDefault) || function (mod) {
3-
return (mod && mod.__esModule) ? mod : { "default": mod };
4-
};
52
Object.defineProperty(exports, "__esModule", { value: true });
63
exports.createIndexBuilder = exports.IndexBuilder = void 0;
74
const fs_1 = require("fs");
8-
const path_1 = __importDefault(require("path"));
95
const constants_1 = require("../core/constants");
10-
const ProjectLayout_1 = require("../utils/ProjectLayout");
11-
const SKIP_DIRS = new Set(['node_modules', 'dist', '.git', 'changes', 'for-ai']);
6+
// Single source of truth for the index algorithm. `build-index.ts` is also compiled to the
7+
// standalone `.ospec/tools/build-index-auto.cjs` used by the git hook; requiring it here means
8+
// the writer and the hook share one implementation (no parser/layout divergence). The tool
9+
// only runs its CLI `main()` under `require.main === module`, so importing it is side-effect free.
10+
const indexTool = require('../tools/build-index');
1211
async function pathExists(targetPath) {
1312
try {
1413
await fs_1.promises.access(targetPath, fs_1.constants.F_OK);
@@ -19,109 +18,27 @@ async function pathExists(targetPath) {
1918
}
2019
}
2120
async function readJson(filePath) {
22-
return JSON.parse((await fs_1.promises.readFile(filePath, 'utf8')).replace(/^\uFEFF/, ''));
21+
return JSON.parse((await fs_1.promises.readFile(filePath, 'utf8')).replace(/^/, ''));
2322
}
2423
class IndexBuilder {
25-
constructor(skillParser) {
26-
this.skillParser = skillParser;
27-
}
24+
// skillParser is retained for backward-compatible construction; the index algorithm now
25+
// lives in the shared build-index tool, so parsing is no longer done here.
26+
constructor(_skillParser) { }
2827
async build(rootDir) {
29-
const config = await this.readProjectConfig(rootDir);
30-
const projectLayout = (0, ProjectLayout_1.getProjectLayout)(config);
31-
const managedRoot = (0, ProjectLayout_1.getProjectManagedRoot)(rootDir, projectLayout);
32-
const modules = {};
33-
const tagIndex = {};
34-
let totalFiles = 0;
35-
let totalSections = 0;
36-
const visit = async (currentDir) => {
37-
const entries = (await fs_1.promises.readdir(currentDir, { withFileTypes: true })).sort((left, right) => left.name.localeCompare(right.name));
38-
for (const entry of entries) {
39-
const fullPath = path_1.default.join(currentDir, entry.name);
40-
if (entry.isDirectory()) {
41-
if (!SKIP_DIRS.has(entry.name)) {
42-
await visit(fullPath);
43-
}
44-
continue;
45-
}
46-
if (entry.name !== constants_1.FILE_NAMES.SKILL_MD) {
47-
continue;
48-
}
49-
totalFiles++;
50-
const relativePath = path_1.default.relative(rootDir, fullPath).replace(/\\/g, '/');
51-
const content = await fs_1.promises.readFile(fullPath, 'utf-8');
52-
const parsed = this.skillParser.parseSkillFile(content);
53-
const moduleName = parsed.frontmatter.name || relativePath;
54-
const title = parsed.frontmatter.title || parsed.frontmatter.name || relativePath;
55-
const tags = parsed.frontmatter.tags || [];
56-
const sections = parsed.sections;
57-
totalSections += Object.keys(sections).length;
58-
modules[moduleName] = {
59-
file: relativePath,
60-
title,
61-
tags,
62-
sections,
63-
};
64-
for (const tag of tags) {
65-
if (!tagIndex[tag]) {
66-
tagIndex[tag] = [];
67-
}
68-
tagIndex[tag].push(moduleName);
69-
}
70-
}
71-
};
72-
if (await pathExists(managedRoot)) {
73-
await visit(managedRoot);
74-
}
75-
const activeChangesDir = (0, ProjectLayout_1.resolveManagedPath)(rootDir, 'changes/active', projectLayout);
76-
const activeChanges = (await pathExists(activeChangesDir))
77-
? (await fs_1.promises.readdir(activeChangesDir)).sort((left, right) => left.localeCompare(right))
78-
: [];
79-
for (const tag of Object.keys(tagIndex)) {
80-
tagIndex[tag] = tagIndex[tag].sort((left, right) => left.localeCompare(right));
81-
}
82-
return {
83-
version: '1.0',
84-
generated: new Date().toISOString(),
85-
git_commit: null,
86-
active_changes: activeChanges,
87-
stats: {
88-
totalFiles,
89-
totalModules: Object.keys(modules).length,
90-
totalSections,
91-
},
92-
modules,
93-
tagIndex,
94-
};
28+
return indexTool.buildIndex(rootDir);
9529
}
9630
async write(rootDir) {
97-
const config = await this.readProjectConfig(rootDir);
98-
const indexPath = (0, ProjectLayout_1.resolveManagedPath)(rootDir, constants_1.FILE_NAMES.SKILL_INDEX, config);
99-
const previous = (await pathExists(indexPath))
100-
? (await readJson(indexPath))
101-
: null;
102-
const index = await this.build(rootDir);
103-
const previousComparable = previous ? this.stripVolatileFields(previous) : null;
104-
const nextComparable = this.stripVolatileFields(index);
105-
if (previous && JSON.stringify(previousComparable) === JSON.stringify(nextComparable)) {
106-
return previous;
107-
}
108-
const output = {
109-
...index,
110-
generated: new Date().toISOString(),
111-
};
112-
await fs_1.promises.writeFile(indexPath, JSON.stringify(output, null, 2), 'utf-8');
113-
return output;
31+
const { index } = await indexTool.writeIndex(rootDir, { silent: true });
32+
return index;
11433
}
11534
async createEmpty(rootDir) {
116-
const config = await this.readProjectConfig(rootDir);
117-
const indexPath = (0, ProjectLayout_1.resolveManagedPath)(rootDir, constants_1.FILE_NAMES.SKILL_INDEX, config);
118-
const previous = (await pathExists(indexPath))
119-
? (await readJson(indexPath))
120-
: null;
35+
const layout = await indexTool.getProjectLayout(rootDir);
36+
const indexPath = indexTool.resolveManagedPath(rootDir, constants_1.FILE_NAMES.SKILL_INDEX, layout);
37+
const previous = (await pathExists(indexPath)) ? await readJson(indexPath) : null;
12138
const index = {
12239
version: '1.0',
12340
generated: new Date().toISOString(),
124-
git_commit: null,
41+
git_commit: indexTool.resolveGitCommit(rootDir),
12542
active_changes: [],
12643
stats: {
12744
totalFiles: 0,
@@ -131,29 +48,17 @@ class IndexBuilder {
13148
modules: {},
13249
tagIndex: {},
13350
};
134-
if (previous && JSON.stringify(this.stripVolatileFields(previous)) === JSON.stringify(this.stripVolatileFields(index))) {
51+
if (previous && JSON.stringify(stripVolatileFields(previous)) === JSON.stringify(stripVolatileFields(index))) {
13552
return previous;
13653
}
13754
await fs_1.promises.writeFile(indexPath, JSON.stringify(index, null, 2), 'utf-8');
13855
return index;
13956
}
140-
stripVolatileFields(index) {
141-
const { generated: _generated, ...stable } = index;
142-
return stable;
143-
}
144-
async readProjectConfig(rootDir) {
145-
const configPath = path_1.default.join(rootDir, constants_1.FILE_NAMES.SKILLRC);
146-
if (!(await pathExists(configPath))) {
147-
return null;
148-
}
149-
try {
150-
return await readJson(configPath);
151-
}
152-
catch {
153-
return null;
154-
}
155-
}
15657
}
15758
exports.IndexBuilder = IndexBuilder;
59+
function stripVolatileFields(index) {
60+
const { generated: _generated, git_commit: _gitCommit, ...stable } = index;
61+
return stable;
62+
}
15863
const createIndexBuilder = (skillParser) => new IndexBuilder(skillParser);
15964
exports.createIndexBuilder = createIndexBuilder;

dist/services/ProjectService.d.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,12 @@ export declare class ProjectService {
224224
getBootstrapFieldPolicy(): BootstrapFieldPolicy[];
225225
getBootstrapStructurePolicy(rootDir: string): BootstrapStructurePolicy;
226226
private buildBootstrapPreview;
227-
rebuildIndex(rootDir: string): Promise<SkillsStatus['skillIndex']>;
227+
rebuildIndex(rootDir: string, options?: {
228+
syncAssets?: boolean;
229+
}): Promise<SkillsStatus['skillIndex']>;
230+
rebuildIndexForPath(targetPath: string, options?: {
231+
syncAssets?: boolean;
232+
}): Promise<SkillsStatus['skillIndex']>;
228233
private getDirectorySkeleton;
229234
private getProtocolShellDirectorySkeleton;
230235
private getKnowledgeLayerDirectorySkeleton;
@@ -266,6 +271,7 @@ export declare class ProjectService {
266271
private analyzeTddEvidenceForVerificationDocument;
267272
private analyzeDebugEvidenceForVerificationDocument;
268273
private maxUpdatedAt;
274+
private collectKnowledgeDocPaths;
269275
private getLatestUpdatedAt;
270276
private shouldRebuildIndex;
271277
private getIndexRebuildReasons;

0 commit comments

Comments
 (0)