@matteeh/osq 0.1.0
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/LICENSE +21 -0
- package/README.md +183 -0
- package/dist/cli/approve.d.ts +6 -0
- package/dist/cli/approve.d.ts.map +1 -0
- package/dist/cli/approve.js +26 -0
- package/dist/cli/approve.js.map +1 -0
- package/dist/cli/bin.d.ts +3 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +5 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/index.d.ts +4 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +76 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +4 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +19 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/new.d.ts +4 -0
- package/dist/cli/new.d.ts.map +1 -0
- package/dist/cli/new.js +15 -0
- package/dist/cli/new.js.map +1 -0
- package/dist/cli/report.d.ts +10 -0
- package/dist/cli/report.d.ts.map +1 -0
- package/dist/cli/report.js +84 -0
- package/dist/cli/report.js.map +1 -0
- package/dist/cli/setup.d.ts +2 -0
- package/dist/cli/setup.d.ts.map +1 -0
- package/dist/cli/setup.js +10 -0
- package/dist/cli/setup.js.map +1 -0
- package/dist/cli/show.d.ts +7 -0
- package/dist/cli/show.d.ts.map +1 -0
- package/dist/cli/show.js +27 -0
- package/dist/cli/show.js.map +1 -0
- package/dist/cli/status.d.ts +7 -0
- package/dist/cli/status.d.ts.map +1 -0
- package/dist/cli/status.js +23 -0
- package/dist/cli/status.js.map +1 -0
- package/dist/cli/watch.d.ts +9 -0
- package/dist/cli/watch.d.ts.map +1 -0
- package/dist/cli/watch.js +20 -0
- package/dist/cli/watch.js.map +1 -0
- package/dist/core/approve.d.ts +11 -0
- package/dist/core/approve.d.ts.map +1 -0
- package/dist/core/approve.js +52 -0
- package/dist/core/approve.js.map +1 -0
- package/dist/core/config.d.ts +53 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +124 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/hasher.d.ts +4 -0
- package/dist/core/hasher.d.ts.map +1 -0
- package/dist/core/hasher.js +49 -0
- package/dist/core/hasher.js.map +1 -0
- package/dist/core/init.d.ts +12 -0
- package/dist/core/init.d.ts.map +1 -0
- package/dist/core/init.js +151 -0
- package/dist/core/init.js.map +1 -0
- package/dist/core/linter.d.ts +8 -0
- package/dist/core/linter.d.ts.map +1 -0
- package/dist/core/linter.js +100 -0
- package/dist/core/linter.js.map +1 -0
- package/dist/core/lock.d.ts +17 -0
- package/dist/core/lock.d.ts.map +1 -0
- package/dist/core/lock.js +104 -0
- package/dist/core/lock.js.map +1 -0
- package/dist/core/logger.d.ts +32 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +106 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/new.d.ts +11 -0
- package/dist/core/new.d.ts.map +1 -0
- package/dist/core/new.js +79 -0
- package/dist/core/new.js.map +1 -0
- package/dist/core/parser.d.ts +32 -0
- package/dist/core/parser.d.ts.map +1 -0
- package/dist/core/parser.js +87 -0
- package/dist/core/parser.js.map +1 -0
- package/dist/core/report.d.ts +54 -0
- package/dist/core/report.d.ts.map +1 -0
- package/dist/core/report.js +475 -0
- package/dist/core/report.js.map +1 -0
- package/dist/core/show.d.ts +50 -0
- package/dist/core/show.d.ts.map +1 -0
- package/dist/core/show.js +374 -0
- package/dist/core/show.js.map +1 -0
- package/dist/core/state.d.ts +25 -0
- package/dist/core/state.d.ts.map +1 -0
- package/dist/core/state.js +180 -0
- package/dist/core/state.js.map +1 -0
- package/dist/core/status.d.ts +10 -0
- package/dist/core/status.d.ts.map +1 -0
- package/dist/core/status.js +106 -0
- package/dist/core/status.js.map +1 -0
- package/dist/harness/agy.d.ts +31 -0
- package/dist/harness/agy.d.ts.map +1 -0
- package/dist/harness/agy.js +251 -0
- package/dist/harness/agy.js.map +1 -0
- package/dist/harness/index.d.ts +7 -0
- package/dist/harness/index.d.ts.map +1 -0
- package/dist/harness/index.js +20 -0
- package/dist/harness/index.js.map +1 -0
- package/dist/harness/mock.d.ts +19 -0
- package/dist/harness/mock.d.ts.map +1 -0
- package/dist/harness/mock.js +62 -0
- package/dist/harness/mock.js.map +1 -0
- package/dist/harness/opencode.d.ts +39 -0
- package/dist/harness/opencode.d.ts.map +1 -0
- package/dist/harness/opencode.js +394 -0
- package/dist/harness/opencode.js.map +1 -0
- package/dist/harness/process.d.ts +26 -0
- package/dist/harness/process.d.ts.map +1 -0
- package/dist/harness/process.js +99 -0
- package/dist/harness/process.js.map +1 -0
- package/dist/harness/stream.d.ts +34 -0
- package/dist/harness/stream.d.ts.map +1 -0
- package/dist/harness/stream.js +75 -0
- package/dist/harness/stream.js.map +1 -0
- package/dist/harness/types.d.ts +53 -0
- package/dist/harness/types.d.ts.map +1 -0
- package/dist/harness/types.js +10 -0
- package/dist/harness/types.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/watcher/archiver.d.ts +5 -0
- package/dist/watcher/archiver.d.ts.map +1 -0
- package/dist/watcher/archiver.js +66 -0
- package/dist/watcher/archiver.js.map +1 -0
- package/dist/watcher/loop.d.ts +34 -0
- package/dist/watcher/loop.d.ts.map +1 -0
- package/dist/watcher/loop.js +235 -0
- package/dist/watcher/loop.js.map +1 -0
- package/dist/watcher/runner.d.ts +70 -0
- package/dist/watcher/runner.d.ts.map +1 -0
- package/dist/watcher/runner.js +604 -0
- package/dist/watcher/runner.js.map +1 -0
- package/package.json +66 -0
- package/templates/.env.example +3 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { parseSpecMd, parseTaskMd } from './parser.js';
|
|
4
|
+
async function checkDependencyExists(projectRoot, depId, specsDirName) {
|
|
5
|
+
const specsDir = path.join(projectRoot, specsDirName);
|
|
6
|
+
const archiveDir = path.join(specsDir, 'archive');
|
|
7
|
+
const dirsToCheck = [specsDir, archiveDir];
|
|
8
|
+
const paddedDep = depId.padStart(3, '0');
|
|
9
|
+
for (const dir of dirsToCheck) {
|
|
10
|
+
try {
|
|
11
|
+
const entries = await fs.readdir(dir);
|
|
12
|
+
for (const entry of entries) {
|
|
13
|
+
if (entry.startsWith(`${paddedDep}-`) || entry === paddedDep) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// Directory may not exist yet
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
export async function lintChangeFolder(projectRoot, folderPath, config) {
|
|
25
|
+
const errors = [];
|
|
26
|
+
const warnings = [];
|
|
27
|
+
const specMdPath = path.join(folderPath, 'spec.md');
|
|
28
|
+
let specContent = '';
|
|
29
|
+
try {
|
|
30
|
+
specContent = await fs.readFile(specMdPath, 'utf8');
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
errors.push('spec.md not found in change folder');
|
|
34
|
+
return { valid: false, errors, warnings };
|
|
35
|
+
}
|
|
36
|
+
const spec = parseSpecMd(specContent);
|
|
37
|
+
// Check: features.writes max entries
|
|
38
|
+
if (spec.features.writes.length > config.limits.maxFeatureWrites) {
|
|
39
|
+
errors.push(`features.writes has ${spec.features.writes.length} entries (max allowed is ${config.limits.maxFeatureWrites})`);
|
|
40
|
+
}
|
|
41
|
+
// Check: contract tables count
|
|
42
|
+
if (spec.contractTablesCount > config.limits.maxContractTables) {
|
|
43
|
+
errors.push(`Contract has ${spec.contractTablesCount} tables (max allowed is ${config.limits.maxContractTables})`);
|
|
44
|
+
}
|
|
45
|
+
// Check: Delta empty while features.writes is not
|
|
46
|
+
if (spec.features.writes.length > 0 && !spec.delta.trim()) {
|
|
47
|
+
errors.push('Delta is empty while features.writes contains entries');
|
|
48
|
+
}
|
|
49
|
+
// Check: depends_on exists
|
|
50
|
+
for (const dep of spec.dependsOn) {
|
|
51
|
+
const exists = await checkDependencyExists(projectRoot, dep, config.paths.specs);
|
|
52
|
+
if (!exists) {
|
|
53
|
+
errors.push(`depends_on names missing change: ${dep}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Tasks checks
|
|
57
|
+
const tasksDir = path.join(folderPath, 'tasks');
|
|
58
|
+
let taskEntries = [];
|
|
59
|
+
try {
|
|
60
|
+
taskEntries = (await fs.readdir(tasksDir)).filter((e) => e.endsWith('.md'));
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
errors.push('tasks directory not found in change folder');
|
|
64
|
+
}
|
|
65
|
+
if (taskEntries.length === 0) {
|
|
66
|
+
errors.push('No task files found under tasks/');
|
|
67
|
+
}
|
|
68
|
+
for (const taskFile of taskEntries) {
|
|
69
|
+
const taskPath = path.join(tasksDir, taskFile);
|
|
70
|
+
const taskContent = await fs.readFile(taskPath, 'utf8');
|
|
71
|
+
const task = parseTaskMd(taskContent);
|
|
72
|
+
// Warning: task title contains " and "
|
|
73
|
+
if (task.title.toLowerCase().includes(' and ')) {
|
|
74
|
+
warnings.push(`Task in ${taskFile} title contains " and ": "${task.title}"`);
|
|
75
|
+
}
|
|
76
|
+
// Check: verify command empty or chains commands
|
|
77
|
+
if (!task.verify) {
|
|
78
|
+
errors.push(`Task in ${taskFile} verify command is empty`);
|
|
79
|
+
}
|
|
80
|
+
else if (task.verify.includes('&&') ||
|
|
81
|
+
task.verify.includes(';') ||
|
|
82
|
+
task.verify.includes('|')) {
|
|
83
|
+
errors.push(`Task in ${taskFile} verify chains commands ("${task.verify}")`);
|
|
84
|
+
}
|
|
85
|
+
// Check: acceptance checklist length
|
|
86
|
+
if (task.acceptance.length > config.limits.maxAcceptanceLines) {
|
|
87
|
+
errors.push(`Task in ${taskFile} acceptance lines (${task.acceptance.length}) exceeds limit ${config.limits.maxAcceptanceLines}`);
|
|
88
|
+
}
|
|
89
|
+
// Check: scope expands to more than maxScopeFiles
|
|
90
|
+
if (task.scope.length > config.limits.maxScopeFiles) {
|
|
91
|
+
errors.push(`Task in ${taskFile} scope specifies ${task.scope.length} patterns, exceeding limit ${config.limits.maxScopeFiles}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
valid: errors.length === 0,
|
|
96
|
+
errors,
|
|
97
|
+
warnings,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=linter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linter.js","sourceRoot":"","sources":["../../src/core/linter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface LockResult {
|
|
2
|
+
acquired: boolean;
|
|
3
|
+
pid: number;
|
|
4
|
+
startedAt: number;
|
|
5
|
+
lockPath: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function acquireLock(runDir: string, taskNumber: string | number): Promise<LockResult>;
|
|
8
|
+
export declare function releaseLock(runDir: string, taskNumber: string | number): Promise<void>;
|
|
9
|
+
export declare function isPidRunning(pid: number): boolean;
|
|
10
|
+
export interface ReapedLock {
|
|
11
|
+
taskNumber: string;
|
|
12
|
+
reason: 'crashed' | 'timeout';
|
|
13
|
+
pid: number;
|
|
14
|
+
startedAt: number;
|
|
15
|
+
}
|
|
16
|
+
export declare function reapStaleLocks(specFolderPath: string, staleLockSeconds: number): Promise<ReapedLock[]>;
|
|
17
|
+
//# sourceMappingURL=lock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock.d.ts","sourceRoot":"","sources":["../../src/core/lock.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAsB,WAAW,CAC/B,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,GAAG,MAAM,GAC1B,OAAO,CAAC,UAAU,CAAC,CAkBrB;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAS5F;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CASjD;AAED,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,SAAS,GAAG,SAAS,CAAC;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,cAAc,CAClC,cAAc,EAAE,MAAM,EACtB,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,UAAU,EAAE,CAAC,CAiEvB"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export async function acquireLock(runDir, taskNumber) {
|
|
4
|
+
const runningDir = path.join(runDir, 'running');
|
|
5
|
+
await fs.mkdir(runningDir, { recursive: true });
|
|
6
|
+
const lockPath = path.join(runningDir, `${taskNumber}.pid`);
|
|
7
|
+
const pid = process.pid;
|
|
8
|
+
const startedAt = Date.now();
|
|
9
|
+
const payload = JSON.stringify({ pid, startedAt });
|
|
10
|
+
try {
|
|
11
|
+
await fs.writeFile(lockPath, payload, { flag: 'wx', encoding: 'utf8' });
|
|
12
|
+
return { acquired: true, pid, startedAt, lockPath };
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
if (err.code === 'EEXIST') {
|
|
16
|
+
return { acquired: false, pid: 0, startedAt: 0, lockPath };
|
|
17
|
+
}
|
|
18
|
+
throw err;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export async function releaseLock(runDir, taskNumber) {
|
|
22
|
+
const lockPath = path.join(runDir, 'running', `${taskNumber}.pid`);
|
|
23
|
+
try {
|
|
24
|
+
await fs.unlink(lockPath);
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
if (err.code !== 'ENOENT') {
|
|
28
|
+
throw err;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function isPidRunning(pid) {
|
|
33
|
+
if (pid <= 0)
|
|
34
|
+
return false;
|
|
35
|
+
try {
|
|
36
|
+
process.kill(pid, 0);
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
const code = err.code;
|
|
41
|
+
return code === 'EPERM';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export async function reapStaleLocks(specFolderPath, staleLockSeconds) {
|
|
45
|
+
const runDir = path.join(specFolderPath, '.run');
|
|
46
|
+
const runningDir = path.join(runDir, 'running');
|
|
47
|
+
const deadDir = path.join(runDir, 'dead');
|
|
48
|
+
let entries = [];
|
|
49
|
+
try {
|
|
50
|
+
entries = await fs.readdir(runningDir);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
const reaped = [];
|
|
56
|
+
const now = Date.now();
|
|
57
|
+
for (const entry of entries) {
|
|
58
|
+
if (!entry.endsWith('.pid'))
|
|
59
|
+
continue;
|
|
60
|
+
const taskNumber = entry.replace(/\.pid$/, '');
|
|
61
|
+
const lockPath = path.join(runningDir, entry);
|
|
62
|
+
let lockData;
|
|
63
|
+
try {
|
|
64
|
+
const content = await fs.readFile(lockPath, 'utf8');
|
|
65
|
+
lockData = JSON.parse(content);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
lockData = { pid: 0, startedAt: now };
|
|
69
|
+
}
|
|
70
|
+
let reapReason = null;
|
|
71
|
+
if (now - lockData.startedAt > staleLockSeconds * 1000) {
|
|
72
|
+
reapReason = 'timeout';
|
|
73
|
+
}
|
|
74
|
+
else if (!isPidRunning(lockData.pid)) {
|
|
75
|
+
reapReason = 'crashed';
|
|
76
|
+
}
|
|
77
|
+
if (reapReason) {
|
|
78
|
+
try {
|
|
79
|
+
await fs.unlink(lockPath);
|
|
80
|
+
}
|
|
81
|
+
catch { }
|
|
82
|
+
await fs.mkdir(deadDir, { recursive: true });
|
|
83
|
+
const deadPath = path.join(deadDir, `${taskNumber}.md`);
|
|
84
|
+
const deadContent = [
|
|
85
|
+
'---',
|
|
86
|
+
`reason: ${reapReason}`,
|
|
87
|
+
`pid: ${lockData.pid}`,
|
|
88
|
+
`started_at: ${new Date(lockData.startedAt).toISOString()}`,
|
|
89
|
+
`reaped_at: ${new Date(now).toISOString()}`,
|
|
90
|
+
'---',
|
|
91
|
+
`Task execution terminated by watcher reaper (${reapReason}).`,
|
|
92
|
+
].join('\n');
|
|
93
|
+
await fs.writeFile(deadPath, `${deadContent}\n`, 'utf8');
|
|
94
|
+
reaped.push({
|
|
95
|
+
taskNumber,
|
|
96
|
+
reason: reapReason,
|
|
97
|
+
pid: lockData.pid,
|
|
98
|
+
startedAt: lockData.startedAt,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return reaped;
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock.js","sourceRoot":"","sources":["../../src/core/lock.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type LogLevel = 'quiet' | 'normal' | 'verbose';
|
|
2
|
+
export interface Logger {
|
|
3
|
+
info(msg: string): void;
|
|
4
|
+
verbose(msg: string): void;
|
|
5
|
+
warn(msg: string): void;
|
|
6
|
+
error(msg: string): void;
|
|
7
|
+
status(text: string): void;
|
|
8
|
+
clearStatus(): void;
|
|
9
|
+
/** True when the sink is an interactive TTY with animation enabled. */
|
|
10
|
+
readonly interactive?: boolean;
|
|
11
|
+
/** True when unicode status symbols may be used (TTY, no `CI`, no `NO_COLOR`). */
|
|
12
|
+
readonly symbols?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Environment-level check for unicode status symbols. Mirrors the logger's own
|
|
16
|
+
* detection: an interactive stderr, no `CI`, and no `NO_COLOR`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function symbolsEnabled(): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Choose between a unicode symbol and its plain-word fallback. Callers that
|
|
21
|
+
* own a logger should pass `logger.symbols` as `enabled` so the choice matches
|
|
22
|
+
* the logger's sink; the default consults the process environment.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveSymbol(symbol: string, word: string, enabled?: boolean): string;
|
|
25
|
+
export interface LoggerOptions {
|
|
26
|
+
stream?: NodeJS.WritableStream & {
|
|
27
|
+
isTTY?: boolean;
|
|
28
|
+
};
|
|
29
|
+
isTTY?: boolean;
|
|
30
|
+
}
|
|
31
|
+
export declare function createLogger(level: LogLevel, prefix?: string, options?: LoggerOptions): Logger;
|
|
32
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/core/logger.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEtD,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,IAAI,IAAI,CAAC;IACpB,uEAAuE;IACvE,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B,kFAAkF;IAClF,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,UAAmB,GAAG,MAAM,CAE9F;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC,cAAc,GAAG;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IACrD,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAqBD,wBAAgB,YAAY,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,MAAM,CA6E9F"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment-level check for unicode status symbols. Mirrors the logger's own
|
|
3
|
+
* detection: an interactive stderr, no `CI`, and no `NO_COLOR`.
|
|
4
|
+
*/
|
|
5
|
+
export function symbolsEnabled() {
|
|
6
|
+
return process.stderr.isTTY === true && !process.env.CI && process.env.NO_COLOR === undefined;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Choose between a unicode symbol and its plain-word fallback. Callers that
|
|
10
|
+
* own a logger should pass `logger.symbols` as `enabled` so the choice matches
|
|
11
|
+
* the logger's sink; the default consults the process environment.
|
|
12
|
+
*/
|
|
13
|
+
export function resolveSymbol(symbol, word, enabled = symbolsEnabled()) {
|
|
14
|
+
return enabled ? symbol : word;
|
|
15
|
+
}
|
|
16
|
+
const LEVEL_RANK = {
|
|
17
|
+
quiet: 0,
|
|
18
|
+
normal: 1,
|
|
19
|
+
verbose: 2,
|
|
20
|
+
};
|
|
21
|
+
const MESSAGE_RANK = {
|
|
22
|
+
verbose: 2,
|
|
23
|
+
info: 1,
|
|
24
|
+
warn: 0,
|
|
25
|
+
error: 0,
|
|
26
|
+
};
|
|
27
|
+
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
28
|
+
const SPINNER_INTERVAL_MS = 80;
|
|
29
|
+
const CLEAR_LINE = '\r\x1b[2K';
|
|
30
|
+
export function createLogger(level, prefix, options) {
|
|
31
|
+
const maxRank = LEVEL_RANK[level];
|
|
32
|
+
const stream = options?.stream ?? process.stderr;
|
|
33
|
+
const interactive = (options?.isTTY ?? stream.isTTY) === true;
|
|
34
|
+
const animate = interactive && level !== 'quiet' && !process.env.CI;
|
|
35
|
+
const symbols = animate && process.env.NO_COLOR === undefined;
|
|
36
|
+
let statusText = null;
|
|
37
|
+
let frameIndex = 0;
|
|
38
|
+
let timer = null;
|
|
39
|
+
const render = (msg) => {
|
|
40
|
+
const lines = msg.split('\n');
|
|
41
|
+
return lines.map((line) => (prefix ? `[${prefix}] ${line}` : line)).join('\n');
|
|
42
|
+
};
|
|
43
|
+
const writeRaw = (chunk) => {
|
|
44
|
+
stream.write(chunk);
|
|
45
|
+
};
|
|
46
|
+
const redraw = () => {
|
|
47
|
+
if (statusText === null)
|
|
48
|
+
return;
|
|
49
|
+
writeRaw(`${CLEAR_LINE}${SPINNER_FRAMES[frameIndex]} ${statusText}`);
|
|
50
|
+
};
|
|
51
|
+
const startTimer = () => {
|
|
52
|
+
if (timer !== null)
|
|
53
|
+
return;
|
|
54
|
+
timer = setInterval(() => {
|
|
55
|
+
frameIndex = (frameIndex + 1) % SPINNER_FRAMES.length;
|
|
56
|
+
redraw();
|
|
57
|
+
}, SPINNER_INTERVAL_MS);
|
|
58
|
+
timer.unref();
|
|
59
|
+
};
|
|
60
|
+
const stopTimer = () => {
|
|
61
|
+
if (timer === null)
|
|
62
|
+
return;
|
|
63
|
+
clearInterval(timer);
|
|
64
|
+
timer = null;
|
|
65
|
+
};
|
|
66
|
+
const write = (messageLevel, msg) => {
|
|
67
|
+
if (MESSAGE_RANK[messageLevel] > maxRank)
|
|
68
|
+
return;
|
|
69
|
+
const rendered = render(msg);
|
|
70
|
+
const line = rendered.endsWith('\n') ? rendered : `${rendered}\n`;
|
|
71
|
+
if (animate && statusText !== null) {
|
|
72
|
+
writeRaw(CLEAR_LINE);
|
|
73
|
+
writeRaw(line);
|
|
74
|
+
redraw();
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
writeRaw(line);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
return {
|
|
81
|
+
interactive: animate,
|
|
82
|
+
symbols,
|
|
83
|
+
info: (msg) => write('info', msg),
|
|
84
|
+
verbose: (msg) => write('verbose', msg),
|
|
85
|
+
warn: (msg) => write('warn', msg),
|
|
86
|
+
error: (msg) => write('error', msg),
|
|
87
|
+
status: (text) => {
|
|
88
|
+
if (!animate)
|
|
89
|
+
return;
|
|
90
|
+
statusText = render(text);
|
|
91
|
+
startTimer();
|
|
92
|
+
redraw();
|
|
93
|
+
},
|
|
94
|
+
clearStatus: () => {
|
|
95
|
+
if (!animate)
|
|
96
|
+
return;
|
|
97
|
+
if (statusText === null && timer === null)
|
|
98
|
+
return;
|
|
99
|
+
writeRaw(CLEAR_LINE);
|
|
100
|
+
stopTimer();
|
|
101
|
+
statusText = null;
|
|
102
|
+
frameIndex = 0;
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/core/logger.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function slugify(text: string): string;
|
|
2
|
+
export declare function getNextSpecNumber(specsDir: string): Promise<string>;
|
|
3
|
+
export interface NewSpecResult {
|
|
4
|
+
specId: string;
|
|
5
|
+
folderName: string;
|
|
6
|
+
folderPath: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function createNewSpec(projectDir: string, title: string, options?: {
|
|
9
|
+
specsDirName?: string;
|
|
10
|
+
}): Promise<NewSpecResult>;
|
|
11
|
+
//# sourceMappingURL=new.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"new.d.ts","sourceRoot":"","sources":["../../src/core/new.ts"],"names":[],"mappings":"AAGA,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAM5C;AAED,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAwBzE;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAA;CAAO,GACtC,OAAO,CAAC,aAAa,CAAC,CAoDxB"}
|
package/dist/core/new.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export function slugify(text) {
|
|
4
|
+
return text
|
|
5
|
+
.toLowerCase()
|
|
6
|
+
.trim()
|
|
7
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
8
|
+
.replace(/^-+|-+$/g, '');
|
|
9
|
+
}
|
|
10
|
+
export async function getNextSpecNumber(specsDir) {
|
|
11
|
+
const dirsToScan = [specsDir, path.join(specsDir, 'archive')];
|
|
12
|
+
let maxNum = 0;
|
|
13
|
+
for (const dir of dirsToScan) {
|
|
14
|
+
let entries = [];
|
|
15
|
+
try {
|
|
16
|
+
entries = await fs.readdir(dir);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
for (const entry of entries) {
|
|
22
|
+
const match = entry.match(/^(\d+)/);
|
|
23
|
+
if (match) {
|
|
24
|
+
const num = Number.parseInt(match[1], 10);
|
|
25
|
+
if (!Number.isNaN(num) && num > maxNum) {
|
|
26
|
+
maxNum = num;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return String(maxNum + 1).padStart(3, '0');
|
|
32
|
+
}
|
|
33
|
+
export async function createNewSpec(projectDir, title, options = {}) {
|
|
34
|
+
const trimmedTitle = title.trim();
|
|
35
|
+
if (!trimmedTitle) {
|
|
36
|
+
throw new Error('Spec name cannot be empty');
|
|
37
|
+
}
|
|
38
|
+
const slug = slugify(trimmedTitle);
|
|
39
|
+
if (!slug) {
|
|
40
|
+
throw new Error('Spec name cannot be empty');
|
|
41
|
+
}
|
|
42
|
+
const specsDir = path.join(projectDir, options.specsDirName || 'specs');
|
|
43
|
+
const templateDir = path.join(specsDir, '_template');
|
|
44
|
+
const templateExists = await fs
|
|
45
|
+
.stat(templateDir)
|
|
46
|
+
.then(() => true)
|
|
47
|
+
.catch(() => false);
|
|
48
|
+
if (!templateExists) {
|
|
49
|
+
throw new Error(`Template directory not found at ${templateDir}. Run osq init first.`);
|
|
50
|
+
}
|
|
51
|
+
const specId = await getNextSpecNumber(specsDir);
|
|
52
|
+
const folderName = `${specId}-${slug}`;
|
|
53
|
+
const targetDir = path.join(specsDir, folderName);
|
|
54
|
+
const targetExists = await fs
|
|
55
|
+
.stat(targetDir)
|
|
56
|
+
.then(() => true)
|
|
57
|
+
.catch(() => false);
|
|
58
|
+
if (targetExists) {
|
|
59
|
+
throw new Error(`Spec folder already exists at ${targetDir}`);
|
|
60
|
+
}
|
|
61
|
+
// Copy template folder recursively
|
|
62
|
+
await fs.cp(templateDir, targetDir, { recursive: true });
|
|
63
|
+
// Update spec.md title
|
|
64
|
+
const specMdPath = path.join(targetDir, 'spec.md');
|
|
65
|
+
try {
|
|
66
|
+
const specContent = await fs.readFile(specMdPath, 'utf8');
|
|
67
|
+
const updatedContent = specContent.replace(/^title:\s*.*$/m, `title: ${trimmedTitle}`);
|
|
68
|
+
await fs.writeFile(specMdPath, updatedContent, 'utf8');
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// If spec.md doesn't exist, proceed
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
specId,
|
|
75
|
+
folderName,
|
|
76
|
+
folderPath: targetDir,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=new.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"new.js","sourceRoot":"","sources":["../../src/core/new.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface FrontmatterResult {
|
|
2
|
+
data: Record<string, unknown>;
|
|
3
|
+
body: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function parseFrontmatter(content: string): FrontmatterResult;
|
|
6
|
+
export interface SpecFeatures {
|
|
7
|
+
readonly reads: string[];
|
|
8
|
+
readonly writes: string[];
|
|
9
|
+
}
|
|
10
|
+
export interface SpecData {
|
|
11
|
+
readonly title: string;
|
|
12
|
+
readonly dependsOn: string[];
|
|
13
|
+
readonly features: SpecFeatures;
|
|
14
|
+
readonly goal: string;
|
|
15
|
+
readonly contract: string;
|
|
16
|
+
readonly contractTablesCount: number;
|
|
17
|
+
readonly nonGoals: string;
|
|
18
|
+
readonly delta: string;
|
|
19
|
+
readonly raw: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function parseSpecMd(content: string): SpecData;
|
|
22
|
+
export interface TaskData {
|
|
23
|
+
readonly title: string;
|
|
24
|
+
readonly verify: string;
|
|
25
|
+
readonly scope: string[];
|
|
26
|
+
readonly entry: string[];
|
|
27
|
+
readonly skills: string[];
|
|
28
|
+
readonly acceptance: string[];
|
|
29
|
+
readonly raw: string;
|
|
30
|
+
}
|
|
31
|
+
export declare function parseTaskMd(content: string): TaskData;
|
|
32
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/core/parser.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,CAenE;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAcD,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,CAoCrD;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,CA8BrD"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import YAML from 'yaml';
|
|
2
|
+
export function parseFrontmatter(content) {
|
|
3
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
4
|
+
if (!match) {
|
|
5
|
+
return { data: {}, body: content };
|
|
6
|
+
}
|
|
7
|
+
try {
|
|
8
|
+
const data = YAML.parse(match[1]) || {};
|
|
9
|
+
return {
|
|
10
|
+
data: typeof data === 'object' && data !== null ? data : {},
|
|
11
|
+
body: match[2] || '',
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return { data: {}, body: content };
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function extractSection(body, heading) {
|
|
19
|
+
const regex = new RegExp(`##\\s+${heading}\\s*\\n([\\s\\S]*?)(?=(?:\\n##\\s+|$))`, 'i');
|
|
20
|
+
const match = body.match(regex);
|
|
21
|
+
return match ? match[1].trim() : '';
|
|
22
|
+
}
|
|
23
|
+
function countMarkdownTables(text) {
|
|
24
|
+
const tableRegex = /\|[^\n]+\|\r?\n\|[-:\s|]+\|\r?\n(?:\|[^\n]+\|\r?\n?)+/g;
|
|
25
|
+
const matches = text.match(tableRegex);
|
|
26
|
+
return matches ? matches.length : 0;
|
|
27
|
+
}
|
|
28
|
+
export function parseSpecMd(content) {
|
|
29
|
+
const { data, body } = parseFrontmatter(content);
|
|
30
|
+
const title = typeof data.title === 'string' ? data.title.trim() : '';
|
|
31
|
+
const dependsOn = Array.isArray(data.depends_on)
|
|
32
|
+
? data.depends_on.map((d) => {
|
|
33
|
+
const str = String(d).trim();
|
|
34
|
+
const num = Number.parseInt(str, 10);
|
|
35
|
+
return !Number.isNaN(num) ? String(num).padStart(3, '0') : str;
|
|
36
|
+
})
|
|
37
|
+
: [];
|
|
38
|
+
const rawFeatures = data.features || {};
|
|
39
|
+
const reads = Array.isArray(rawFeatures.reads)
|
|
40
|
+
? rawFeatures.reads.map((r) => String(r).trim())
|
|
41
|
+
: [];
|
|
42
|
+
const writes = Array.isArray(rawFeatures.writes)
|
|
43
|
+
? rawFeatures.writes.map((w) => String(w).trim())
|
|
44
|
+
: [];
|
|
45
|
+
const goal = extractSection(body, 'Goal');
|
|
46
|
+
const contract = extractSection(body, 'Contract');
|
|
47
|
+
const nonGoals = extractSection(body, 'Non-goals');
|
|
48
|
+
const delta = extractSection(body, 'Delta');
|
|
49
|
+
return {
|
|
50
|
+
title,
|
|
51
|
+
dependsOn,
|
|
52
|
+
features: { reads, writes },
|
|
53
|
+
goal,
|
|
54
|
+
contract,
|
|
55
|
+
contractTablesCount: countMarkdownTables(contract),
|
|
56
|
+
nonGoals,
|
|
57
|
+
delta,
|
|
58
|
+
raw: content,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export function parseTaskMd(content) {
|
|
62
|
+
const { data, body } = parseFrontmatter(content);
|
|
63
|
+
const title = typeof data.title === 'string' ? data.title.trim() : '';
|
|
64
|
+
const verify = typeof data.verify === 'string' ? data.verify.trim() : '';
|
|
65
|
+
const scope = Array.isArray(data.scope) ? data.scope.map((s) => String(s).trim()) : [];
|
|
66
|
+
const entry = Array.isArray(data.entry) ? data.entry.map((e) => String(e).trim()) : [];
|
|
67
|
+
const skills = Array.isArray(data.skills)
|
|
68
|
+
? data.skills.map((s) => String(s).trim())
|
|
69
|
+
: [];
|
|
70
|
+
const acceptanceSection = extractSection(body, 'Acceptance') || body;
|
|
71
|
+
const itemRegex = /^[*-]\s+\[[ xX]\]\s+(.+)$/gm;
|
|
72
|
+
const matches = acceptanceSection.matchAll(itemRegex);
|
|
73
|
+
const acceptance = [];
|
|
74
|
+
for (const match of matches) {
|
|
75
|
+
acceptance.push(match[1].trim());
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
title,
|
|
79
|
+
verify,
|
|
80
|
+
scope,
|
|
81
|
+
entry,
|
|
82
|
+
skills,
|
|
83
|
+
acceptance,
|
|
84
|
+
raw: content,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/core/parser.ts"],"names":[],"mappings":"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"}
|