@open-agent-toolkit/cli 0.1.72 → 0.1.73
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 +1 -0
- package/assets/docs/cli-utilities/configuration.md +7 -0
- package/assets/docs/cli-utilities/index.md +1 -0
- package/assets/docs/cli-utilities/project-log.md +183 -0
- package/assets/docs/reference/cli-reference.md +1 -0
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/oat-project-autonomous/references/gate-inventory.md +3 -2
- package/assets/skills/oat-project-complete/SKILL.md +60 -1
- package/assets/skills/oat-project-document/references/docs/autonomy-contract.md +3 -2
- package/assets/skills/oat-project-implement/SKILL.md +18 -2
- package/assets/skills/oat-project-implement/references/docs/autonomy-contract.md +3 -2
- package/assets/skills/oat-project-pr-final/references/docs/autonomy-contract.md +3 -2
- package/assets/skills/oat-project-quick-start/references/docs/autonomy-contract.md +3 -2
- package/assets/skills/oat-project-summary/SKILL.md +122 -18
- package/assets/templates/project-log.md +47 -0
- package/dist/commands/config/index.d.ts.map +1 -1
- package/dist/commands/config/index.js +41 -0
- package/dist/commands/gate/index.d.ts +2 -0
- package/dist/commands/gate/index.d.ts.map +1 -1
- package/dist/commands/gate/index.js +99 -4
- package/dist/commands/init/tools/shared/skill-manifest.d.ts +1 -1
- package/dist/commands/init/tools/shared/skill-manifest.d.ts.map +1 -1
- package/dist/commands/init/tools/shared/skill-manifest.js +1 -0
- package/dist/commands/project/index.d.ts.map +1 -1
- package/dist/commands/project/index.js +2 -0
- package/dist/commands/project/log/append.d.ts +41 -0
- package/dist/commands/project/log/append.d.ts.map +1 -0
- package/dist/commands/project/log/append.js +241 -0
- package/dist/commands/project/log/check.d.ts +66 -0
- package/dist/commands/project/log/check.d.ts.map +1 -0
- package/dist/commands/project/log/check.js +194 -0
- package/dist/commands/project/log/grammar.d.ts +28 -0
- package/dist/commands/project/log/grammar.d.ts.map +1 -0
- package/dist/commands/project/log/grammar.js +31 -0
- package/dist/commands/project/log/index.d.ts +9 -0
- package/dist/commands/project/log/index.d.ts.map +1 -0
- package/dist/commands/project/log/index.js +13 -0
- package/dist/commands/project/log/rollup.d.ts +40 -0
- package/dist/commands/project/log/rollup.d.ts.map +1 -0
- package/dist/commands/project/log/rollup.js +203 -0
- package/dist/commands/project/log/synthesize.d.ts +23 -0
- package/dist/commands/project/log/synthesize.d.ts.map +1 -0
- package/dist/commands/project/log/synthesize.js +112 -0
- package/dist/commands/project/new/index.d.ts +1 -0
- package/dist/commands/project/new/index.d.ts.map +1 -1
- package/dist/commands/project/new/index.js +7 -0
- package/dist/commands/project/new/scaffold.d.ts +1 -0
- package/dist/commands/project/new/scaffold.d.ts.map +1 -1
- package/dist/commands/project/new/scaffold.js +24 -0
- package/dist/config/oat-config.d.ts +3 -0
- package/dist/config/oat-config.d.ts.map +1 -1
- package/dist/config/oat-config.js +7 -0
- package/dist/config/resolve.d.ts.map +1 -1
- package/dist/config/resolve.js +2 -0
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skill-manifest.d.ts","sourceRoot":"","sources":["../../../../../src/commands/init/tools/shared/skill-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAgBH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC;AAED,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAKtD,CAAC;AAEF,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE5E;AAID,eAAO,MAAM,eAAe,s3BAmClB,CAAC;AAEX,eAAO,MAAM,eAAe,oFAIlB,CAAC;AAEX,eAAO,MAAM,kBAAkB,
|
|
1
|
+
{"version":3,"file":"skill-manifest.d.ts","sourceRoot":"","sources":["../../../../../src/commands/init/tools/shared/skill-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAgBH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC;AAED,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAKtD,CAAC;AAEF,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE5E;AAID,eAAO,MAAM,eAAe,s3BAmClB,CAAC;AAEX,eAAO,MAAM,eAAe,oFAIlB,CAAC;AAEX,eAAO,MAAM,kBAAkB,+HASrB,CAAC;AAEX,eAAO,MAAM,gBAAgB,qFAInB,CAAC;AAIX,eAAO,MAAM,WAAW,2FAKd,CAAC;AAIX,eAAO,MAAM,WAAW,qCAAsC,CAAC;AAI/D,eAAO,MAAM,WAAW,iLAQd,CAAC;AAEX,eAAO,MAAM,YAAY,kCAAmC,CAAC;AAI7D,eAAO,MAAM,cAAc,2NASjB,CAAC;AAIX,eAAO,MAAM,yBAAyB,sHAK5B,CAAC;AAEX,eAAO,MAAM,4BAA4B,yIAQ/B,CAAC;AAEX,eAAO,MAAM,0BAA0B,aAAc,CAAC;AAItD,eAAO,MAAM,iBAAiB,6BAA8B,CAAC;AAI7D,eAAO,MAAM,eAAe,2EAMlB,CAAC;AAEX,eAAO,MAAM,eAAe,qCAAsC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/project/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/project/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgBpC,wBAAgB,oBAAoB,IAAI,OAAO,CAgB9C"}
|
|
@@ -4,6 +4,7 @@ import { createProjectCompleteDiscoveryCommand } from './complete-discovery/inde
|
|
|
4
4
|
import { createProjectCompleteStateCommand } from './complete-state/index.js';
|
|
5
5
|
import { createProjectDispatchCeilingCommand } from './dispatch-ceiling/index.js';
|
|
6
6
|
import { createProjectListCommand } from './list.js';
|
|
7
|
+
import { createProjectLogCommand } from './log/index.js';
|
|
7
8
|
import { createProjectNewCommand } from './new/index.js';
|
|
8
9
|
import { createProjectOpenCommand } from './open/index.js';
|
|
9
10
|
import { createProjectPauseCommand } from './pause/index.js';
|
|
@@ -19,6 +20,7 @@ export function createProjectCommand() {
|
|
|
19
20
|
.addCommand(createProjectCompleteStateCommand())
|
|
20
21
|
.addCommand(createProjectDispatchCeilingCommand())
|
|
21
22
|
.addCommand(createProjectListCommand())
|
|
23
|
+
.addCommand(createProjectLogCommand())
|
|
22
24
|
.addCommand(createProjectNewCommand())
|
|
23
25
|
.addCommand(createProjectOpenCommand())
|
|
24
26
|
.addCommand(createProjectPauseCommand())
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type CommandContext, type GlobalOptions } from '../../../app/command-context.js';
|
|
2
|
+
import { type ActiveProjectResolution } from '../../../config/oat-config.js';
|
|
3
|
+
import { type ResolvedConfig } from '../../../config/resolve.js';
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
export interface AppendProjectLogInput {
|
|
6
|
+
repoRoot: string;
|
|
7
|
+
home?: string;
|
|
8
|
+
project?: string;
|
|
9
|
+
structural?: boolean;
|
|
10
|
+
type?: string;
|
|
11
|
+
scope?: string;
|
|
12
|
+
area?: string;
|
|
13
|
+
producer?: string;
|
|
14
|
+
ref?: string;
|
|
15
|
+
body?: string;
|
|
16
|
+
versionNote?: string;
|
|
17
|
+
}
|
|
18
|
+
export type ProjectLogAppendResult = {
|
|
19
|
+
status: 'appended';
|
|
20
|
+
logPath: string;
|
|
21
|
+
heading: string;
|
|
22
|
+
created: boolean;
|
|
23
|
+
} | {
|
|
24
|
+
status: 'skipped';
|
|
25
|
+
reason: 'projectLog=false';
|
|
26
|
+
};
|
|
27
|
+
export interface AppendProjectLogDependencies {
|
|
28
|
+
resolveActiveProject: (repoRoot: string) => Promise<ActiveProjectResolution>;
|
|
29
|
+
resolveEffectiveConfig: (repoRoot: string, userConfigDir: string, env?: NodeJS.ProcessEnv) => Promise<ResolvedConfig>;
|
|
30
|
+
resolveAssetsRoot: () => Promise<string>;
|
|
31
|
+
now: () => Date;
|
|
32
|
+
}
|
|
33
|
+
export interface ProjectLogAppendCommandDependencies extends AppendProjectLogDependencies {
|
|
34
|
+
buildCommandContext: (options: GlobalOptions) => CommandContext;
|
|
35
|
+
resolveProjectRoot: (cwd: string) => Promise<string>;
|
|
36
|
+
readStdin: () => Promise<string>;
|
|
37
|
+
}
|
|
38
|
+
export declare function instantiateProjectLogTemplate(template: string, projectName: string, date: string): string;
|
|
39
|
+
export declare function appendProjectLog(input: AppendProjectLogInput, overrides?: Partial<AppendProjectLogDependencies>): Promise<ProjectLogAppendResult>;
|
|
40
|
+
export declare function createProjectLogAppendCommand(overrides?: Partial<ProjectLogAppendCommandDependencies>): Command;
|
|
41
|
+
//# sourceMappingURL=append.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"append.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/log/append.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAA0B,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAI9E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkBpC,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,MAAM,sBAAsB,GAC9B;IACE,MAAM,EAAE,UAAU,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;CAClB,GACD;IACE,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,EAAE,kBAAkB,CAAC;CAC5B,CAAC;AAEN,MAAM,WAAW,4BAA4B;IAC3C,oBAAoB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAC7E,sBAAsB,EAAE,CACtB,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,KACpB,OAAO,CAAC,cAAc,CAAC,CAAC;IAC7B,iBAAiB,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACzC,GAAG,EAAE,MAAM,IAAI,CAAC;CACjB;AAqBD,MAAM,WAAW,mCAAoC,SAAQ,4BAA4B;IACvF,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,SAAS,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;CAClC;AA4LD,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,GACX,MAAM,CAOR;AAiCD,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,qBAAqB,EAC5B,SAAS,GAAE,OAAO,CAAC,4BAA4B,CAAM,GACpD,OAAO,CAAC,sBAAsB,CAAC,CAoCjC;AAsCD,wBAAgB,6BAA6B,CAC3C,SAAS,GAAE,OAAO,CAAC,mCAAmC,CAAM,GAC3D,OAAO,CA8CT"}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { appendFile, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { basename, isAbsolute, join, resolve } from 'node:path';
|
|
4
|
+
import { buildCommandContext, } from '../../../app/command-context.js';
|
|
5
|
+
import { readGlobalOptions } from '../../shared/shared.utils.js';
|
|
6
|
+
import { resolveActiveProject, } from '../../../config/oat-config.js';
|
|
7
|
+
import { resolveEffectiveConfig } from '../../../config/resolve.js';
|
|
8
|
+
import { resolveAssetsRoot } from '../../../fs/assets.js';
|
|
9
|
+
import { dirExists, fileExists } from '../../../fs/io.js';
|
|
10
|
+
import { resolveProjectRoot } from '../../../fs/paths.js';
|
|
11
|
+
import { Command } from 'commander';
|
|
12
|
+
import { composeJudgmentHeading, composeStructuralHeading, isProjectLogEntryMarker, isProjectLogSectionMarker, PROJECT_LOG_AREA_MAX_LENGTH, PROJECT_LOG_HEADING_DELIMITER, PROJECT_LOG_SCOPES, PROJECT_LOG_TYPES, } from './grammar.js';
|
|
13
|
+
const PROJECT_LOG_FILENAME = 'project-log.md';
|
|
14
|
+
const SYNTHESIS_HEADING_PREFIX = '\n## End-of-run synthesis';
|
|
15
|
+
const DEFAULT_APPEND_DEPENDENCIES = {
|
|
16
|
+
resolveActiveProject,
|
|
17
|
+
resolveEffectiveConfig,
|
|
18
|
+
resolveAssetsRoot,
|
|
19
|
+
now: () => new Date(),
|
|
20
|
+
};
|
|
21
|
+
const DEFAULT_COMMAND_DEPENDENCIES = {
|
|
22
|
+
...DEFAULT_APPEND_DEPENDENCIES,
|
|
23
|
+
buildCommandContext,
|
|
24
|
+
resolveProjectRoot,
|
|
25
|
+
readStdin: async () => {
|
|
26
|
+
const chunks = [];
|
|
27
|
+
for await (const chunk of process.stdin) {
|
|
28
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
29
|
+
}
|
|
30
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
function validateSingleLine(value, option, maxLength = PROJECT_LOG_AREA_MAX_LENGTH) {
|
|
34
|
+
const normalized = value?.trim() ?? '';
|
|
35
|
+
if (!normalized) {
|
|
36
|
+
throw new Error(`${option} is required and must be a non-empty single line.`);
|
|
37
|
+
}
|
|
38
|
+
if (/[\r\n]/.test(value ?? '')) {
|
|
39
|
+
throw new Error(`${option} must be a single line without newline characters.`);
|
|
40
|
+
}
|
|
41
|
+
if (normalized.length > maxLength) {
|
|
42
|
+
throw new Error(`${option} must be at most ${maxLength} characters.`);
|
|
43
|
+
}
|
|
44
|
+
return normalized;
|
|
45
|
+
}
|
|
46
|
+
function validateHeadingField(value, option) {
|
|
47
|
+
const normalized = validateSingleLine(value, option);
|
|
48
|
+
if (normalized.includes(PROJECT_LOG_HEADING_DELIMITER)) {
|
|
49
|
+
throw new Error(`${option} must not contain the project-log heading delimiter '${PROJECT_LOG_HEADING_DELIMITER}'.`);
|
|
50
|
+
}
|
|
51
|
+
return normalized;
|
|
52
|
+
}
|
|
53
|
+
function validateVersionNote(value) {
|
|
54
|
+
if (value === undefined) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
return validateSingleLine(value, '--version-note', Number.MAX_SAFE_INTEGER);
|
|
58
|
+
}
|
|
59
|
+
function containsCommandOwnedMarker(body) {
|
|
60
|
+
return body
|
|
61
|
+
.split(/\r?\n/)
|
|
62
|
+
.some((line) => isProjectLogSectionMarker(line) || isProjectLogEntryMarker(line));
|
|
63
|
+
}
|
|
64
|
+
function validateEntry(input, date) {
|
|
65
|
+
const body = input.body?.trim();
|
|
66
|
+
if (!body) {
|
|
67
|
+
throw new Error('--body is required and must contain non-whitespace text.');
|
|
68
|
+
}
|
|
69
|
+
const versionNote = validateVersionNote(input.versionNote);
|
|
70
|
+
if (input.structural) {
|
|
71
|
+
if (input.type !== undefined ||
|
|
72
|
+
input.scope !== undefined ||
|
|
73
|
+
input.area !== undefined) {
|
|
74
|
+
throw new Error('--structural cannot be combined with judgment flags --type, --scope, or --area.');
|
|
75
|
+
}
|
|
76
|
+
if (/[\r\n]/.test(body)) {
|
|
77
|
+
throw new Error('--body for structural entries must be one line without newline characters.');
|
|
78
|
+
}
|
|
79
|
+
if (containsCommandOwnedMarker(body)) {
|
|
80
|
+
throw new Error('--body for structural entries must not contain command-owned level-two or level-three Markdown headings.');
|
|
81
|
+
}
|
|
82
|
+
const producer = validateHeadingField(input.producer, '--producer');
|
|
83
|
+
const ref = validateHeadingField(input.ref, '--ref');
|
|
84
|
+
return {
|
|
85
|
+
heading: composeStructuralHeading({ date, producer, ref }),
|
|
86
|
+
body,
|
|
87
|
+
versionNote,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (input.producer !== undefined || input.ref !== undefined) {
|
|
91
|
+
throw new Error('Judgment entries cannot use structural flags --producer or --ref; add --structural or remove those flags.');
|
|
92
|
+
}
|
|
93
|
+
if (!input.type) {
|
|
94
|
+
throw new Error(`--type is required for judgment entries; accepted values: ${PROJECT_LOG_TYPES.join(' | ')}.`);
|
|
95
|
+
}
|
|
96
|
+
if (!PROJECT_LOG_TYPES.includes(input.type)) {
|
|
97
|
+
throw new Error(`Invalid --type '${input.type}'; accepted values: ${PROJECT_LOG_TYPES.join(' | ')}.`);
|
|
98
|
+
}
|
|
99
|
+
if (!input.scope) {
|
|
100
|
+
throw new Error(`--scope is required for judgment entries; accepted values: ${PROJECT_LOG_SCOPES.join(' | ')}.`);
|
|
101
|
+
}
|
|
102
|
+
if (!PROJECT_LOG_SCOPES.includes(input.scope)) {
|
|
103
|
+
throw new Error(`Invalid --scope '${input.scope}'; accepted values: ${PROJECT_LOG_SCOPES.join(' | ')}.`);
|
|
104
|
+
}
|
|
105
|
+
if (containsCommandOwnedMarker(body)) {
|
|
106
|
+
throw new Error('--body for judgment entries must not contain command-owned level-two or level-three Markdown headings.');
|
|
107
|
+
}
|
|
108
|
+
const area = validateHeadingField(input.area, '--area');
|
|
109
|
+
return {
|
|
110
|
+
heading: composeJudgmentHeading({
|
|
111
|
+
date,
|
|
112
|
+
type: input.type,
|
|
113
|
+
scope: input.scope,
|
|
114
|
+
area,
|
|
115
|
+
}),
|
|
116
|
+
body,
|
|
117
|
+
versionNote,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
async function resolveTargetProject(input, dependencies) {
|
|
121
|
+
if (input.project) {
|
|
122
|
+
const projectPath = isAbsolute(input.project)
|
|
123
|
+
? resolve(input.project)
|
|
124
|
+
: resolve(input.repoRoot, input.project);
|
|
125
|
+
if (!(await dirExists(projectPath)) ||
|
|
126
|
+
!(await fileExists(join(projectPath, 'state.md')))) {
|
|
127
|
+
throw new Error(`Project path is missing or invalid: ${input.project}. Pass --project <path> for a directory containing state.md.`);
|
|
128
|
+
}
|
|
129
|
+
return projectPath;
|
|
130
|
+
}
|
|
131
|
+
const active = await dependencies.resolveActiveProject(input.repoRoot);
|
|
132
|
+
if (active.status !== 'active' || !active.path) {
|
|
133
|
+
throw new Error('No active project resolves. Set activeProject or pass --project <path>.');
|
|
134
|
+
}
|
|
135
|
+
return resolve(input.repoRoot, active.path);
|
|
136
|
+
}
|
|
137
|
+
export function instantiateProjectLogTemplate(template, projectName, date) {
|
|
138
|
+
const instantiated = template
|
|
139
|
+
.replaceAll('{Project Name}', projectName)
|
|
140
|
+
.replaceAll('YYYY-MM-DD', date)
|
|
141
|
+
.replaceAll(/\n?oat_template:\s*true\s*\n/gi, '\n')
|
|
142
|
+
.replaceAll(/\n?oat_template_name:\s*[^\n]*\n/gi, '\n');
|
|
143
|
+
return instantiated.endsWith('\n') ? instantiated : `${instantiated}\n`;
|
|
144
|
+
}
|
|
145
|
+
async function appendEntry(logPath, heading, body, versionNote) {
|
|
146
|
+
const bodyWithVersion = versionNote?.trim()
|
|
147
|
+
? `${body} (observed on ${versionNote.trim()})`
|
|
148
|
+
: body;
|
|
149
|
+
const entry = `\n${heading}\n\n${bodyWithVersion}\n`;
|
|
150
|
+
const content = await readFile(logPath, 'utf8');
|
|
151
|
+
const synthesisIndex = content.indexOf(SYNTHESIS_HEADING_PREFIX);
|
|
152
|
+
if (synthesisIndex >= 0) {
|
|
153
|
+
await writeFile(logPath, `${content.slice(0, synthesisIndex)}${entry}${content.slice(synthesisIndex)}`, 'utf8');
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
await appendFile(logPath, `${content.endsWith('\n') ? '' : '\n'}${entry}`, 'utf8');
|
|
157
|
+
}
|
|
158
|
+
export async function appendProjectLog(input, overrides = {}) {
|
|
159
|
+
const dependencies = { ...DEFAULT_APPEND_DEPENDENCIES, ...overrides };
|
|
160
|
+
const projectPath = await resolveTargetProject(input, dependencies);
|
|
161
|
+
const logPath = join(projectPath, PROJECT_LOG_FILENAME);
|
|
162
|
+
const logExists = await fileExists(logPath);
|
|
163
|
+
if (!logExists) {
|
|
164
|
+
const effective = await dependencies.resolveEffectiveConfig(input.repoRoot, join(input.home ?? homedir(), '.oat'));
|
|
165
|
+
if (effective.resolved['workflow.projectLog']?.value === false) {
|
|
166
|
+
return { status: 'skipped', reason: 'projectLog=false' };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const date = dependencies.now().toISOString().slice(0, 10);
|
|
170
|
+
const { heading, body, versionNote } = validateEntry(input, date);
|
|
171
|
+
let created = false;
|
|
172
|
+
if (!logExists) {
|
|
173
|
+
const assetsRoot = await dependencies.resolveAssetsRoot();
|
|
174
|
+
const template = await readFile(join(assetsRoot, 'templates', PROJECT_LOG_FILENAME), 'utf8');
|
|
175
|
+
await writeFile(logPath, instantiateProjectLogTemplate(template, basename(projectPath), date), 'utf8');
|
|
176
|
+
created = true;
|
|
177
|
+
}
|
|
178
|
+
await appendEntry(logPath, heading, body, versionNote);
|
|
179
|
+
return { status: 'appended', logPath, heading, created };
|
|
180
|
+
}
|
|
181
|
+
async function runAppendCommand(options, context, dependencies) {
|
|
182
|
+
try {
|
|
183
|
+
const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
|
|
184
|
+
const body = options.body === '-' ? await dependencies.readStdin() : options.body;
|
|
185
|
+
const result = await appendProjectLog({
|
|
186
|
+
repoRoot,
|
|
187
|
+
home: context.home,
|
|
188
|
+
...options,
|
|
189
|
+
body,
|
|
190
|
+
}, dependencies);
|
|
191
|
+
if (context.json) {
|
|
192
|
+
context.logger.json(result);
|
|
193
|
+
}
|
|
194
|
+
else if (result.status === 'appended') {
|
|
195
|
+
context.logger.success(`Appended project log entry: ${result.logPath}`);
|
|
196
|
+
}
|
|
197
|
+
process.exitCode = 0;
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
201
|
+
if (context.json) {
|
|
202
|
+
context.logger.json({ status: 'error', message });
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
context.logger.error(message);
|
|
206
|
+
}
|
|
207
|
+
process.exitCode = 1;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
export function createProjectLogAppendCommand(overrides = {}) {
|
|
211
|
+
const dependencies = { ...DEFAULT_COMMAND_DEPENDENCIES, ...overrides };
|
|
212
|
+
return new Command('append')
|
|
213
|
+
.description('Append a validated entry to the active project log')
|
|
214
|
+
.option('--structural', 'Write a structural lifecycle entry')
|
|
215
|
+
.option('--type <type>', 'Judgment type: bug, friction, worked-well, feedback')
|
|
216
|
+
.option('--scope <scope>', 'Judgment scope: project or general')
|
|
217
|
+
.option('--area <area>', 'Short single-line judgment area (maximum 120 characters)')
|
|
218
|
+
.option('--producer <producer>', 'Structural producer skill or command')
|
|
219
|
+
.option('--ref <ref>', 'Structural phase, scope, or artifact reference')
|
|
220
|
+
.option('--body <text>', 'Entry body, or - to read the body from stdin')
|
|
221
|
+
.option('--version-note <text>', 'Append an observed-on version clause')
|
|
222
|
+
.option('--project <path>', 'Explicit project path; defaults to the active project')
|
|
223
|
+
.addHelpText('after', `
|
|
224
|
+
Entry contract:
|
|
225
|
+
Log breaks, surprises, workarounds, or notable successes; record evidence, not narrative.
|
|
226
|
+
worked-well entries are do-not-regress evidence. Judgment bodies should use 1–3 sentences.
|
|
227
|
+
High-value judgments may use Observation:, Impact:, and Recommendation: fields.
|
|
228
|
+
Structural bodies are one line and reference artifacts by path instead of inlining them.
|
|
229
|
+
Add --version-note for tool-related observations.
|
|
230
|
+
Never record secret values (tokens, keys, signed URLs, or credentials); reference secrets by name or source.
|
|
231
|
+
Prior entries are never edited or struck through. Append a new judgment entry that references and explains a correction.
|
|
232
|
+
|
|
233
|
+
Heading grammars:
|
|
234
|
+
### YYYY-MM-DD · <project|general> · <bug|friction|worked-well|feedback> · <area>
|
|
235
|
+
### YYYY-MM-DD · structural · <producer> · <ref>
|
|
236
|
+
`)
|
|
237
|
+
.action(async (options, command) => {
|
|
238
|
+
const context = dependencies.buildCommandContext(readGlobalOptions(command));
|
|
239
|
+
await runAppendCommand(options, context, dependencies);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type CommandContext, type GlobalOptions } from '../../../app/command-context.js';
|
|
2
|
+
import { type ActiveProjectResolution } from '../../../config/oat-config.js';
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { type ProjectLogSection, type ProjectLogScope, type ProjectLogType } from './grammar.js';
|
|
5
|
+
export declare const PROJECT_LOG_FILENAME = "project-log.md";
|
|
6
|
+
export declare const SYNTHESIS_PENDING_HEADING = "## End-of-run synthesis (pending \u2014 do not skip at project completion)";
|
|
7
|
+
export declare const SYNTHESIS_COMPLETE_HEADING = "## End-of-run synthesis";
|
|
8
|
+
export interface ProjectLogCheckResult {
|
|
9
|
+
status: 'ok' | 'absent' | 'synthesis_pending';
|
|
10
|
+
logPath: string | null;
|
|
11
|
+
entryCounts: {
|
|
12
|
+
structural: number;
|
|
13
|
+
judgment: Record<ProjectLogType, number>;
|
|
14
|
+
};
|
|
15
|
+
scopeCounts: Record<ProjectLogScope, number>;
|
|
16
|
+
lastEntryDate: string | null;
|
|
17
|
+
synthesisPending: boolean;
|
|
18
|
+
grammarViolations: string[];
|
|
19
|
+
}
|
|
20
|
+
export type ParsedProjectLogEntry = {
|
|
21
|
+
class: 'judgment';
|
|
22
|
+
heading: string;
|
|
23
|
+
date: string;
|
|
24
|
+
scope: ProjectLogScope;
|
|
25
|
+
type: ProjectLogType;
|
|
26
|
+
area: string;
|
|
27
|
+
body: string;
|
|
28
|
+
} | {
|
|
29
|
+
class: 'structural';
|
|
30
|
+
heading: string;
|
|
31
|
+
date: string;
|
|
32
|
+
producer: string;
|
|
33
|
+
ref: string;
|
|
34
|
+
body: string;
|
|
35
|
+
};
|
|
36
|
+
export interface ParsedProjectLog {
|
|
37
|
+
entries: ParsedProjectLogEntry[];
|
|
38
|
+
grammarViolations: string[];
|
|
39
|
+
}
|
|
40
|
+
export type ProjectLogSynthesisSection = {
|
|
41
|
+
status: 'pending';
|
|
42
|
+
section: ProjectLogSection;
|
|
43
|
+
} | {
|
|
44
|
+
status: 'complete';
|
|
45
|
+
section: ProjectLogSection;
|
|
46
|
+
} | {
|
|
47
|
+
status: 'missing';
|
|
48
|
+
} | {
|
|
49
|
+
status: 'ambiguous';
|
|
50
|
+
};
|
|
51
|
+
export interface CheckProjectLogInput {
|
|
52
|
+
repoRoot: string;
|
|
53
|
+
project?: string;
|
|
54
|
+
}
|
|
55
|
+
export interface CheckProjectLogDependencies {
|
|
56
|
+
resolveActiveProject: (repoRoot: string) => Promise<ActiveProjectResolution>;
|
|
57
|
+
}
|
|
58
|
+
export interface ProjectLogCheckCommandDependencies extends CheckProjectLogDependencies {
|
|
59
|
+
buildCommandContext: (options: GlobalOptions) => CommandContext;
|
|
60
|
+
resolveProjectRoot: (cwd: string) => Promise<string>;
|
|
61
|
+
}
|
|
62
|
+
export declare function findCanonicalProjectLogSynthesisSection(content: string): ProjectLogSynthesisSection;
|
|
63
|
+
export declare function parseProjectLogEntries(content: string): ParsedProjectLog;
|
|
64
|
+
export declare function checkProjectLog(input: CheckProjectLogInput, overrides?: Partial<CheckProjectLogDependencies>): Promise<ProjectLogCheckResult>;
|
|
65
|
+
export declare function createProjectLogCheckCommand(overrides?: Partial<ProjectLogCheckCommandDependencies>): Command;
|
|
66
|
+
//# sourceMappingURL=check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/log/check.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { isAbsolute, join, resolve } from 'node:path';
|
|
3
|
+
import { buildCommandContext, } from '../../../app/command-context.js';
|
|
4
|
+
import { readGlobalOptions } from '../../shared/shared.utils.js';
|
|
5
|
+
import { resolveActiveProject, } from '../../../config/oat-config.js';
|
|
6
|
+
import { dirExists, fileExists } from '../../../fs/io.js';
|
|
7
|
+
import { resolveProjectRoot } from '../../../fs/paths.js';
|
|
8
|
+
import { Command } from 'commander';
|
|
9
|
+
import { findProjectLogSections, isProjectLogEntryMarker, JUDGMENT_HEADING_RE, PROJECT_LOG_TYPES, STRUCTURAL_HEADING_RE, } from './grammar.js';
|
|
10
|
+
export const PROJECT_LOG_FILENAME = 'project-log.md';
|
|
11
|
+
export const SYNTHESIS_PENDING_HEADING = '## End-of-run synthesis (pending — do not skip at project completion)';
|
|
12
|
+
export const SYNTHESIS_COMPLETE_HEADING = '## End-of-run synthesis';
|
|
13
|
+
const DEFAULT_CHECK_DEPENDENCIES = {
|
|
14
|
+
resolveActiveProject,
|
|
15
|
+
};
|
|
16
|
+
const DEFAULT_COMMAND_DEPENDENCIES = {
|
|
17
|
+
...DEFAULT_CHECK_DEPENDENCIES,
|
|
18
|
+
buildCommandContext,
|
|
19
|
+
resolveProjectRoot,
|
|
20
|
+
};
|
|
21
|
+
function emptyCounts() {
|
|
22
|
+
return {
|
|
23
|
+
entryCounts: {
|
|
24
|
+
structural: 0,
|
|
25
|
+
judgment: {
|
|
26
|
+
bug: 0,
|
|
27
|
+
friction: 0,
|
|
28
|
+
'worked-well': 0,
|
|
29
|
+
feedback: 0,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
scopeCounts: { project: 0, general: 0 },
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function entriesSection(content) {
|
|
36
|
+
const section = findProjectLogSections(content).find(({ heading }) => heading === '## Entries');
|
|
37
|
+
if (!section) {
|
|
38
|
+
return '';
|
|
39
|
+
}
|
|
40
|
+
return content.slice(section.start + section.heading.length, section.end);
|
|
41
|
+
}
|
|
42
|
+
export function findCanonicalProjectLogSynthesisSection(content) {
|
|
43
|
+
const candidates = findProjectLogSections(content).filter(({ heading }) => heading === SYNTHESIS_PENDING_HEADING ||
|
|
44
|
+
heading === SYNTHESIS_COMPLETE_HEADING);
|
|
45
|
+
if (candidates.length === 0) {
|
|
46
|
+
return { status: 'missing' };
|
|
47
|
+
}
|
|
48
|
+
if (candidates.length !== 1) {
|
|
49
|
+
return { status: 'ambiguous' };
|
|
50
|
+
}
|
|
51
|
+
const section = candidates[0];
|
|
52
|
+
return {
|
|
53
|
+
status: section.heading === SYNTHESIS_PENDING_HEADING ? 'pending' : 'complete',
|
|
54
|
+
section,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export function parseProjectLogEntries(content) {
|
|
58
|
+
const lines = entriesSection(content).split('\n');
|
|
59
|
+
const entries = [];
|
|
60
|
+
const grammarViolations = [];
|
|
61
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
62
|
+
const heading = lines[index];
|
|
63
|
+
if (!heading || !isProjectLogEntryMarker(heading)) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const bodyLines = [];
|
|
67
|
+
let cursor = index + 1;
|
|
68
|
+
while (cursor < lines.length &&
|
|
69
|
+
!isProjectLogEntryMarker(lines[cursor] ?? '')) {
|
|
70
|
+
bodyLines.push(lines[cursor] ?? '');
|
|
71
|
+
cursor += 1;
|
|
72
|
+
}
|
|
73
|
+
const body = bodyLines.join('\n').trim();
|
|
74
|
+
index = cursor - 1;
|
|
75
|
+
const judgment = JUDGMENT_HEADING_RE.exec(heading);
|
|
76
|
+
if (judgment) {
|
|
77
|
+
entries.push({
|
|
78
|
+
class: 'judgment',
|
|
79
|
+
heading,
|
|
80
|
+
date: judgment[1],
|
|
81
|
+
scope: judgment[2],
|
|
82
|
+
type: judgment[3],
|
|
83
|
+
area: judgment[4].trim(),
|
|
84
|
+
body,
|
|
85
|
+
});
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const structural = STRUCTURAL_HEADING_RE.exec(heading);
|
|
89
|
+
if (structural) {
|
|
90
|
+
entries.push({
|
|
91
|
+
class: 'structural',
|
|
92
|
+
heading,
|
|
93
|
+
date: structural[1],
|
|
94
|
+
producer: structural[2].trim(),
|
|
95
|
+
ref: structural[3].trim(),
|
|
96
|
+
body,
|
|
97
|
+
});
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
grammarViolations.push(heading);
|
|
101
|
+
}
|
|
102
|
+
return { entries, grammarViolations };
|
|
103
|
+
}
|
|
104
|
+
async function resolveTargetProject(input, dependencies) {
|
|
105
|
+
if (input.project) {
|
|
106
|
+
const projectPath = isAbsolute(input.project)
|
|
107
|
+
? resolve(input.project)
|
|
108
|
+
: resolve(input.repoRoot, input.project);
|
|
109
|
+
if (!(await dirExists(projectPath)) ||
|
|
110
|
+
!(await fileExists(join(projectPath, 'state.md')))) {
|
|
111
|
+
throw new Error(`Project path is missing or invalid: ${input.project}. Pass --project <path> for a directory containing state.md.`);
|
|
112
|
+
}
|
|
113
|
+
return projectPath;
|
|
114
|
+
}
|
|
115
|
+
const active = await dependencies.resolveActiveProject(input.repoRoot);
|
|
116
|
+
if (active.status !== 'active' || !active.path) {
|
|
117
|
+
throw new Error('No active project resolves. Set activeProject or pass --project <path>.');
|
|
118
|
+
}
|
|
119
|
+
return resolve(input.repoRoot, active.path);
|
|
120
|
+
}
|
|
121
|
+
export async function checkProjectLog(input, overrides = {}) {
|
|
122
|
+
const dependencies = { ...DEFAULT_CHECK_DEPENDENCIES, ...overrides };
|
|
123
|
+
const projectPath = await resolveTargetProject(input, dependencies);
|
|
124
|
+
const logPath = join(projectPath, PROJECT_LOG_FILENAME);
|
|
125
|
+
if (!(await fileExists(logPath))) {
|
|
126
|
+
return {
|
|
127
|
+
status: 'absent',
|
|
128
|
+
logPath: null,
|
|
129
|
+
...emptyCounts(),
|
|
130
|
+
lastEntryDate: null,
|
|
131
|
+
synthesisPending: false,
|
|
132
|
+
grammarViolations: [],
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
const content = await readFile(logPath, 'utf8');
|
|
136
|
+
const parsed = parseProjectLogEntries(content);
|
|
137
|
+
const counts = emptyCounts();
|
|
138
|
+
for (const entry of parsed.entries) {
|
|
139
|
+
if (entry.class === 'structural') {
|
|
140
|
+
counts.entryCounts.structural += 1;
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
counts.entryCounts.judgment[entry.type] += 1;
|
|
144
|
+
counts.scopeCounts[entry.scope] += 1;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const synthesisPending = findProjectLogSections(content).some(({ heading }) => heading === SYNTHESIS_PENDING_HEADING);
|
|
148
|
+
return {
|
|
149
|
+
status: synthesisPending ? 'synthesis_pending' : 'ok',
|
|
150
|
+
logPath,
|
|
151
|
+
...counts,
|
|
152
|
+
lastEntryDate: parsed.entries.at(-1)?.date ?? null,
|
|
153
|
+
synthesisPending,
|
|
154
|
+
grammarViolations: parsed.grammarViolations,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
async function runCheckCommand(options, context, dependencies) {
|
|
158
|
+
try {
|
|
159
|
+
const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
|
|
160
|
+
const result = await checkProjectLog({ repoRoot, project: options.project }, dependencies);
|
|
161
|
+
if (context.json) {
|
|
162
|
+
context.logger.json(result);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
context.logger.info(result.status === 'absent'
|
|
166
|
+
? 'Project log: absent'
|
|
167
|
+
: `Project log: ${result.status}; entries: ${result.entryCounts.structural +
|
|
168
|
+
PROJECT_LOG_TYPES.reduce((total, type) => total + result.entryCounts.judgment[type], 0)}`);
|
|
169
|
+
}
|
|
170
|
+
process.exitCode =
|
|
171
|
+
options.requireSynthesis && result.synthesisPending ? 1 : 0;
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
175
|
+
if (context.json) {
|
|
176
|
+
context.logger.json({ status: 'error', message });
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
context.logger.error(message);
|
|
180
|
+
}
|
|
181
|
+
process.exitCode = 1;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
export function createProjectLogCheckCommand(overrides = {}) {
|
|
185
|
+
const dependencies = { ...DEFAULT_COMMAND_DEPENDENCIES, ...overrides };
|
|
186
|
+
return new Command('check')
|
|
187
|
+
.description('Inspect project-log entries and synthesis status')
|
|
188
|
+
.option('--project <path>', 'Explicit project path; defaults to the active project')
|
|
189
|
+
.option('--require-synthesis', 'Exit 1 when the project log still has pending synthesis')
|
|
190
|
+
.action(async (options, command) => {
|
|
191
|
+
const context = dependencies.buildCommandContext(readGlobalOptions(command));
|
|
192
|
+
await runCheckCommand(options, context, dependencies);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare const PROJECT_LOG_TYPES: readonly ["bug", "friction", "worked-well", "feedback"];
|
|
2
|
+
export declare const PROJECT_LOG_SCOPES: readonly ["project", "general"];
|
|
3
|
+
export type ProjectLogType = (typeof PROJECT_LOG_TYPES)[number];
|
|
4
|
+
export type ProjectLogScope = (typeof PROJECT_LOG_SCOPES)[number];
|
|
5
|
+
export declare const PROJECT_LOG_AREA_MAX_LENGTH = 120;
|
|
6
|
+
export declare const PROJECT_LOG_HEADING_DELIMITER = "\u00B7";
|
|
7
|
+
export declare const JUDGMENT_HEADING_RE: RegExp;
|
|
8
|
+
export declare const STRUCTURAL_HEADING_RE: RegExp;
|
|
9
|
+
export interface ProjectLogSection {
|
|
10
|
+
heading: string;
|
|
11
|
+
start: number;
|
|
12
|
+
end: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function isProjectLogSectionMarker(line: string): boolean;
|
|
15
|
+
export declare function isProjectLogEntryMarker(line: string): boolean;
|
|
16
|
+
export declare function findProjectLogSections(content: string): ProjectLogSection[];
|
|
17
|
+
export declare function composeJudgmentHeading(input: {
|
|
18
|
+
date: string;
|
|
19
|
+
scope: ProjectLogScope;
|
|
20
|
+
type: ProjectLogType;
|
|
21
|
+
area: string;
|
|
22
|
+
}): string;
|
|
23
|
+
export declare function composeStructuralHeading(input: {
|
|
24
|
+
date: string;
|
|
25
|
+
producer: string;
|
|
26
|
+
ref: string;
|
|
27
|
+
}): string;
|
|
28
|
+
//# sourceMappingURL=grammar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grammar.d.ts","sourceRoot":"","sources":["../../../../src/commands/project/log/grammar.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iBAAiB,yDAKpB,CAAC;AACX,eAAO,MAAM,kBAAkB,iCAAkC,CAAC;AAElE,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAChE,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAElE,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAC/C,eAAO,MAAM,6BAA6B,WAAM,CAAC;AAEjD,eAAO,MAAM,mBAAmB,QACqE,CAAC;AACtG,eAAO,MAAM,qBAAqB,QACoC,CAAC;AAEvE,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE/D;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE7D;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,EAAE,CAO3E;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,eAAe,CAAC;IACvB,IAAI,EAAE,cAAc,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,MAAM,CAET;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb,GAAG,MAAM,CAET"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export const PROJECT_LOG_TYPES = [
|
|
2
|
+
'bug',
|
|
3
|
+
'friction',
|
|
4
|
+
'worked-well',
|
|
5
|
+
'feedback',
|
|
6
|
+
];
|
|
7
|
+
export const PROJECT_LOG_SCOPES = ['project', 'general'];
|
|
8
|
+
export const PROJECT_LOG_AREA_MAX_LENGTH = 120;
|
|
9
|
+
export const PROJECT_LOG_HEADING_DELIMITER = '·';
|
|
10
|
+
export const JUDGMENT_HEADING_RE = /^### (\d{4}-\d{2}-\d{2}) · (project|general) · (bug|friction|worked-well|feedback) · ([^·\r\n]+)$/;
|
|
11
|
+
export const STRUCTURAL_HEADING_RE = /^### (\d{4}-\d{2}-\d{2}) · structural · ([^·\r\n]+) · ([^·\r\n]+)$/;
|
|
12
|
+
export function isProjectLogSectionMarker(line) {
|
|
13
|
+
return /^## .+$/.test(line);
|
|
14
|
+
}
|
|
15
|
+
export function isProjectLogEntryMarker(line) {
|
|
16
|
+
return /^### .+$/.test(line);
|
|
17
|
+
}
|
|
18
|
+
export function findProjectLogSections(content) {
|
|
19
|
+
const matches = [...content.matchAll(/^## [^\r\n]+/gm)];
|
|
20
|
+
return matches.map((match, index) => ({
|
|
21
|
+
heading: match[0],
|
|
22
|
+
start: match.index,
|
|
23
|
+
end: matches[index + 1]?.index ?? content.length,
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
export function composeJudgmentHeading(input) {
|
|
27
|
+
return `### ${input.date} · ${input.scope} · ${input.type} · ${input.area}`;
|
|
28
|
+
}
|
|
29
|
+
export function composeStructuralHeading(input) {
|
|
30
|
+
return `### ${input.date} · structural · ${input.producer} · ${input.ref}`;
|
|
31
|
+
}
|