project-tiny-context-harness 0.2.53 → 0.2.55
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 +122 -69
- package/assets/README.md +113 -62
- package/assets/README.zh-CN.md +8 -6
- package/assets/agents/AGENTS_CORE.md +17 -16
- package/assets/context_templates/product-surface-contract.md +60 -0
- package/assets/github/harness.yml +15 -11
- package/assets/make/ty-context.mk +48 -0
- package/assets/skills/context_development_engineer/SKILL.md +9 -6
- package/assets/skills/context_full_project_export/SKILL.md +13 -13
- package/assets/skills/context_harness_upgrade/SKILL.md +9 -9
- package/assets/skills/context_product_plan/SKILL.md +7 -4
- package/assets/skills/context_surface_contract/SKILL.md +168 -0
- package/assets/skills/context_uiux_design/SKILL.md +7 -4
- package/assets/skills/plan_acceptance_checklist_compiler/SKILL.md +427 -0
- package/assets/tools/validate_context.py +1 -1
- package/dist/cli.js +1 -1
- package/dist/commands/check-modularity.js +14 -6
- package/dist/commands/export-context.js +4 -4
- package/dist/commands/index.js +8 -5
- package/dist/commands/init.js +1 -1
- package/dist/commands/package-source.js +1 -1
- package/dist/commands/upgrade.js +1 -1
- package/dist/lib/config.js +7 -2
- package/dist/lib/constants.d.ts +1 -1
- package/dist/lib/constants.js +1 -1
- package/dist/lib/context-export.js +5 -5
- package/dist/lib/harness-root.d.ts +5 -0
- package/dist/lib/harness-root.js +32 -4
- package/dist/lib/legacy-managed-scan.d.ts +2 -0
- package/dist/lib/legacy-managed-scan.js +79 -0
- package/dist/lib/legacy-sdlc-migration.d.ts +2 -0
- package/dist/lib/legacy-sdlc-migration.js +189 -0
- package/dist/lib/managed-file.d.ts +18 -12
- package/dist/lib/managed-file.js +25 -14
- package/dist/lib/migrations.js +4 -2
- package/dist/lib/modularity.d.ts +9 -0
- package/dist/lib/modularity.js +132 -8
- package/dist/lib/package-json-config.js +3 -3
- package/dist/lib/paths.d.ts +2 -2
- package/dist/lib/paths.js +2 -2
- package/dist/lib/sync-engine.js +33 -31
- package/dist/lib/types.d.ts +12 -0
- package/dist/lib/validators.js +37 -4
- package/package.json +5 -5
- package/source-mappings.yaml +13 -13
- package/assets/make/sdlc-harness.mk +0 -43
package/dist/lib/modularity.js
CHANGED
|
@@ -2,9 +2,13 @@ import { execFile } from "node:child_process";
|
|
|
2
2
|
import { promises as fs } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { promisify } from "node:util";
|
|
5
|
+
import { readConfig } from "./config.js";
|
|
5
6
|
import { shouldIncludeCodeFile, toPosix } from "./source-files.js";
|
|
6
7
|
const execFileAsync = promisify(execFile);
|
|
7
8
|
const GIT_MAX_BUFFER = 16 * 1024 * 1024;
|
|
9
|
+
const DEFAULT_LINE_LIMIT = 300;
|
|
10
|
+
const DEFAULT_MODULARITY_POLICY = "scoped_waivers";
|
|
11
|
+
const MODULARITY_POLICIES = new Set([DEFAULT_MODULARITY_POLICY, "strict_except_generated"]);
|
|
8
12
|
const GENERATED_FILE_PATTERNS = [
|
|
9
13
|
/^\s*(?:\/\/|#|--|;)\s*@generated\b/im,
|
|
10
14
|
/^\s*(?:\/\/|#|--|;)\s*code generated .*do not edit\.?\s*$/im,
|
|
@@ -13,8 +17,18 @@ const GENERATED_FILE_PATTERNS = [
|
|
|
13
17
|
/^\s*(?:\/\/|#|--|;)\s*this file was generated\b/im,
|
|
14
18
|
/^\s*(?:\/\/|#|--|;)\s*generated by\b/im
|
|
15
19
|
];
|
|
20
|
+
const WAIVER_CATEGORIES = new Set([
|
|
21
|
+
"generated",
|
|
22
|
+
"third_party_reference",
|
|
23
|
+
"legacy_migration",
|
|
24
|
+
"aggregate_styles",
|
|
25
|
+
"fixture_snapshot"
|
|
26
|
+
]);
|
|
16
27
|
export async function runModularityCheck(projectRoot, options) {
|
|
17
|
-
const
|
|
28
|
+
const config = await readConfig(projectRoot);
|
|
29
|
+
const { limit: configuredLimit, waiverValues, errors: configErrors } = validateModularityConfig(config.modularity);
|
|
30
|
+
const { waivers, errors: waiverErrors } = validateWaivers(projectRoot, waiverValues);
|
|
31
|
+
const limit = options.limit ?? configuredLimit ?? DEFAULT_LINE_LIMIT;
|
|
18
32
|
const candidates = new Set();
|
|
19
33
|
for (const file of options.files ?? []) {
|
|
20
34
|
candidates.add(normalizeExplicitPath(projectRoot, file));
|
|
@@ -39,12 +53,16 @@ export async function runModularityCheck(projectRoot, options) {
|
|
|
39
53
|
continue;
|
|
40
54
|
}
|
|
41
55
|
const lines = countPhysicalLines(await fs.readFile(absolutePath, "utf8"));
|
|
42
|
-
|
|
56
|
+
const waiver = waivers.get(relativePath);
|
|
57
|
+
files.push({ relativePath, lines, overLimit: lines > limit, waived: waiver });
|
|
43
58
|
}
|
|
44
59
|
const warnings = files
|
|
45
|
-
.filter((file) => file.overLimit)
|
|
60
|
+
.filter((file) => file.overLimit && !file.waived)
|
|
46
61
|
.map((file) => `${file.relativePath}: ${file.lines} physical lines exceeds limit ${limit}`);
|
|
47
|
-
|
|
62
|
+
const waivedWarnings = files
|
|
63
|
+
.filter((file) => file.overLimit && file.waived)
|
|
64
|
+
.map((file) => `${file.relativePath}: ${file.lines} physical lines exceeds limit ${limit} but is waived as ${file.waived?.category}`);
|
|
65
|
+
return { limit, files, warnings, waivedWarnings, errors: [...configErrors, ...waiverErrors] };
|
|
48
66
|
}
|
|
49
67
|
export function countPhysicalLines(content) {
|
|
50
68
|
if (content.length === 0) {
|
|
@@ -57,10 +75,19 @@ export function countPhysicalLines(content) {
|
|
|
57
75
|
return lines.length;
|
|
58
76
|
}
|
|
59
77
|
async function gitTouchedFiles(projectRoot) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
78
|
+
let result;
|
|
79
|
+
try {
|
|
80
|
+
result = await execFileAsync("git", ["-C", projectRoot, "status", "--porcelain", "-z"], {
|
|
81
|
+
encoding: "utf8",
|
|
82
|
+
maxBuffer: GIT_MAX_BUFFER
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
if (isNotGitRepositoryError(error)) {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
64
91
|
const records = result.stdout.split("\0").filter(Boolean);
|
|
65
92
|
const files = [];
|
|
66
93
|
for (let index = 0; index < records.length; index += 1) {
|
|
@@ -83,6 +110,13 @@ async function gitDiffFiles(projectRoot, base) {
|
|
|
83
110
|
});
|
|
84
111
|
return result.stdout.split("\0").filter(Boolean);
|
|
85
112
|
}
|
|
113
|
+
function isNotGitRepositoryError(error) {
|
|
114
|
+
if (!error || typeof error !== "object") {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
const stderr = "stderr" in error && typeof error.stderr === "string" ? error.stderr : "";
|
|
118
|
+
return /not a git repository/i.test(stderr);
|
|
119
|
+
}
|
|
86
120
|
function normalizeExplicitPath(projectRoot, value) {
|
|
87
121
|
const absolute = path.isAbsolute(value) ? path.resolve(value) : path.resolve(projectRoot, value);
|
|
88
122
|
const relative = toPosix(path.relative(projectRoot, absolute));
|
|
@@ -94,6 +128,96 @@ function normalizeExplicitPath(projectRoot, value) {
|
|
|
94
128
|
function normalizeGitPath(value) {
|
|
95
129
|
return toPosix(value).replace(/^\.\//, "");
|
|
96
130
|
}
|
|
131
|
+
function validateModularityConfig(modularity) {
|
|
132
|
+
const errors = [];
|
|
133
|
+
if (modularity === undefined) {
|
|
134
|
+
return { errors };
|
|
135
|
+
}
|
|
136
|
+
if (!modularity || typeof modularity !== "object" || Array.isArray(modularity)) {
|
|
137
|
+
errors.push("<harnessRoot>/config.yaml modularity must be an object");
|
|
138
|
+
return { errors };
|
|
139
|
+
}
|
|
140
|
+
const value = modularity;
|
|
141
|
+
let limit;
|
|
142
|
+
let policy = DEFAULT_MODULARITY_POLICY;
|
|
143
|
+
if (value.limit !== undefined) {
|
|
144
|
+
if (!Number.isInteger(value.limit) || Number(value.limit) <= 0) {
|
|
145
|
+
errors.push("<harnessRoot>/config.yaml modularity.limit must be a positive integer");
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
limit = Number(value.limit);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (value.policy !== undefined) {
|
|
152
|
+
if (typeof value.policy !== "string" || !MODULARITY_POLICIES.has(value.policy)) {
|
|
153
|
+
errors.push("<harnessRoot>/config.yaml modularity.policy must be one of scoped_waivers, strict_except_generated");
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
policy = value.policy;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (policy === "strict_except_generated" && value.waivers !== undefined) {
|
|
160
|
+
errors.push("<harnessRoot>/config.yaml modularity.waivers is not allowed when modularity.policy is strict_except_generated");
|
|
161
|
+
}
|
|
162
|
+
return { limit, waiverValues: policy === "scoped_waivers" ? value.waivers : undefined, errors };
|
|
163
|
+
}
|
|
164
|
+
function validateWaivers(projectRoot, waiverValues) {
|
|
165
|
+
const waivers = new Map();
|
|
166
|
+
const errors = [];
|
|
167
|
+
if (waiverValues === undefined) {
|
|
168
|
+
return { waivers, errors };
|
|
169
|
+
}
|
|
170
|
+
if (!Array.isArray(waiverValues)) {
|
|
171
|
+
errors.push("<harnessRoot>/config.yaml modularity.waivers must be an array");
|
|
172
|
+
return { waivers, errors };
|
|
173
|
+
}
|
|
174
|
+
for (const [index, waiver] of waiverValues.entries()) {
|
|
175
|
+
const label = `<harnessRoot>/config.yaml modularity.waivers[${index}]`;
|
|
176
|
+
if (!waiver || typeof waiver !== "object") {
|
|
177
|
+
errors.push(`${label} must be an object`);
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const relativePath = requiredWaiverString(waiver, "path", label, errors);
|
|
181
|
+
const category = requiredWaiverString(waiver, "category", label, errors);
|
|
182
|
+
const reason = requiredWaiverString(waiver, "reason", label, errors);
|
|
183
|
+
const futureSplitBoundary = requiredWaiverString(waiver, "future_split_boundary", label, errors);
|
|
184
|
+
if (!relativePath || !category || !reason || !futureSplitBoundary) {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (!WAIVER_CATEGORIES.has(category)) {
|
|
188
|
+
errors.push(`${label}.category must be one of generated, third_party_reference, legacy_migration, aggregate_styles, fixture_snapshot`);
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
let normalized;
|
|
192
|
+
try {
|
|
193
|
+
normalized = normalizeExplicitPath(projectRoot, relativePath);
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
errors.push(`${label}.path must stay inside the project root: ${relativePath}`);
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
const existing = waivers.get(normalized);
|
|
200
|
+
if (existing) {
|
|
201
|
+
errors.push(`${label}.path duplicates an existing modularity waiver for ${normalized}`);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
waivers.set(normalized, {
|
|
205
|
+
relativePath: normalized,
|
|
206
|
+
category,
|
|
207
|
+
reason,
|
|
208
|
+
futureSplitBoundary
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
return { waivers, errors };
|
|
212
|
+
}
|
|
213
|
+
function requiredWaiverString(waiver, field, label, errors) {
|
|
214
|
+
const value = waiver[field];
|
|
215
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
216
|
+
errors.push(`${label}.${field} must be a non-empty string`);
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
return value.trim();
|
|
220
|
+
}
|
|
97
221
|
async function isRegularFile(target) {
|
|
98
222
|
try {
|
|
99
223
|
return (await fs.stat(target)).isFile();
|
|
@@ -7,7 +7,7 @@ export async function packageHarnessRoot(projectRoot) {
|
|
|
7
7
|
return undefined;
|
|
8
8
|
}
|
|
9
9
|
const packageJson = parsePackageJson(await readText(packagePath));
|
|
10
|
-
const config = packageJson.
|
|
10
|
+
const config = packageJson.tyContext;
|
|
11
11
|
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
12
12
|
return undefined;
|
|
13
13
|
}
|
|
@@ -18,13 +18,13 @@ export async function writePackageHarnessRoot(projectRoot, folderName) {
|
|
|
18
18
|
const normalized = normalizeHarnessFolderName(folderName);
|
|
19
19
|
const packagePath = path.join(projectRoot, "package.json");
|
|
20
20
|
const packageJson = (await pathExists(packagePath)) ? parsePackageJson(await readText(packagePath)) : {};
|
|
21
|
-
const existingConfig = packageJson.
|
|
21
|
+
const existingConfig = packageJson.tyContext;
|
|
22
22
|
const nextConfig = existingConfig && typeof existingConfig === "object" && !Array.isArray(existingConfig)
|
|
23
23
|
? { ...existingConfig, harnessFolderName: normalized }
|
|
24
24
|
: { harnessFolderName: normalized };
|
|
25
25
|
const next = {
|
|
26
26
|
...packageJson,
|
|
27
|
-
|
|
27
|
+
tyContext: nextConfig
|
|
28
28
|
};
|
|
29
29
|
return writeTextIfChanged(packagePath, `${JSON.stringify(next, null, 2)}\n`);
|
|
30
30
|
}
|
package/dist/lib/paths.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export declare const SOURCE_MAPPINGS_PATH = "packages/
|
|
1
|
+
export declare const SOURCE_MAPPINGS_PATH = "packages/ty-context/source-mappings.yaml";
|
|
2
2
|
export declare const DEFAULT_HARNESS_ROOT = ".agent";
|
|
3
|
-
export declare const HARNESS_JSON_CONFIG_PATH = "
|
|
3
|
+
export declare const HARNESS_JSON_CONFIG_PATH = "ty-context.config.json";
|
|
4
4
|
export declare function packageRoot(): string;
|
|
5
5
|
export declare function packageAssetPath(...segments: string[]): string;
|
package/dist/lib/paths.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
|
-
export const SOURCE_MAPPINGS_PATH = "packages/
|
|
3
|
+
export const SOURCE_MAPPINGS_PATH = "packages/ty-context/source-mappings.yaml";
|
|
4
4
|
export const DEFAULT_HARNESS_ROOT = ".agent";
|
|
5
|
-
export const HARNESS_JSON_CONFIG_PATH = "
|
|
5
|
+
export const HARNESS_JSON_CONFIG_PATH = "ty-context.config.json";
|
|
6
6
|
export function packageRoot() {
|
|
7
7
|
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
8
8
|
}
|
package/dist/lib/sync-engine.js
CHANGED
|
@@ -3,7 +3,7 @@ import { promises as fs } from "node:fs";
|
|
|
3
3
|
import { readConfig } from "./config.js";
|
|
4
4
|
import { harnessPath, harnessRoot } from "./harness-root.js";
|
|
5
5
|
import { copyTree, listFiles, pathExists, readText, writeTextIfChanged } from "./fs.js";
|
|
6
|
-
import { AGENTS_BLOCK_MARKERS, GITHUB_WORKFLOW_BLOCK_END, GITHUB_WORKFLOW_BLOCK_START, MAKEFILE_BLOCK_END, MAKEFILE_BLOCK_MARKERS, MAKEFILE_BLOCK_START, MANAGED_BLOCK_END, MANAGED_BLOCK_START } from "./managed-file.js";
|
|
6
|
+
import { AGENTS_BLOCK_MARKERS, GITHUB_WORKFLOW_BLOCK_END, GITHUB_WORKFLOW_BLOCK_MARKERS, GITHUB_WORKFLOW_BLOCK_START, MAKEFILE_BLOCK_END, MAKEFILE_BLOCK_MARKERS, MAKEFILE_BLOCK_START, MANAGED_BLOCK_END, MANAGED_BLOCK_START } from "./managed-file.js";
|
|
7
7
|
import { packageAssetPath } from "./paths.js";
|
|
8
8
|
import { assertSupportedSchema } from "./schema-guard.js";
|
|
9
9
|
import { createUpgradePlan, formatUpgradePlan, hasUpgradePlanWork } from "./migrations.js";
|
|
@@ -50,20 +50,20 @@ async function syncManagedFile(projectRoot, root, managedFile, report) {
|
|
|
50
50
|
return;
|
|
51
51
|
}
|
|
52
52
|
const managedPath = normalizeManagedPath(managedFile.path);
|
|
53
|
-
if (managedPath === harnessPath(root, "
|
|
53
|
+
if (managedPath === harnessPath(root, "ty-context-managed", "templates")) {
|
|
54
54
|
await syncTree(packageAssetPath("templates"), destination, report);
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
57
|
-
if (managedPath === harnessPath(root, "
|
|
57
|
+
if (managedPath === harnessPath(root, "ty-context-managed", "context_templates")) {
|
|
58
58
|
await syncTree(packageAssetPath("context_templates"), destination, report, { prune: true });
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
61
|
-
if (managedPath === harnessPath(root, "
|
|
61
|
+
if (managedPath === harnessPath(root, "ty-context-managed", "policies")) {
|
|
62
62
|
await syncTree(packageAssetPath("policies"), destination, report);
|
|
63
63
|
return;
|
|
64
64
|
}
|
|
65
|
-
if (managedPath === harnessPath(root, "
|
|
66
|
-
await syncFile(packageAssetPath("make", "
|
|
65
|
+
if (managedPath === harnessPath(root, "ty-context-managed", "make", "ty-context.mk")) {
|
|
66
|
+
await syncFile(packageAssetPath("make", "ty-context.mk"), destination, report, "skip-if-missing");
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
69
69
|
if (managedFile.path === "tools") {
|
|
@@ -118,7 +118,7 @@ function renderAgentsCore(content, root) {
|
|
|
118
118
|
async function syncMakefileInclude(destination, root, report) {
|
|
119
119
|
const existing = (await pathExists(destination)) ? await readText(destination) : "";
|
|
120
120
|
const resetDefaultGoal = shouldResetMakeDefaultGoal(existing);
|
|
121
|
-
const includePath = `${root.replace(/\\/g, "/")}/
|
|
121
|
+
const includePath = `${root.replace(/\\/g, "/")}/ty-context-managed/make/ty-context.mk`;
|
|
122
122
|
const blockLines = [
|
|
123
123
|
MAKEFILE_BLOCK_START,
|
|
124
124
|
"# Included before project targets so project recipes win on name conflicts.",
|
|
@@ -264,22 +264,31 @@ async function syncSkillsTree(source, destination, report) {
|
|
|
264
264
|
}
|
|
265
265
|
}
|
|
266
266
|
async function blockDeprecatedSkillOverrides(projectRoot, root, report) {
|
|
267
|
-
const overrideRoot
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
267
|
+
for (const overrideRoot of skillOverrideRoots(projectRoot, root)) {
|
|
268
|
+
if (!(await pathExists(overrideRoot.absolute))) {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
const deprecatedFiles = (await listFiles(overrideRoot.absolute))
|
|
272
|
+
.filter((file) => path.basename(file) !== ".gitkeep")
|
|
273
|
+
.map((file) => path.relative(overrideRoot.absolute, file).split(path.sep).join("/"))
|
|
274
|
+
.sort();
|
|
275
|
+
if (deprecatedFiles.length === 0) {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
report.blocked.push(`${overrideRoot.relative}: Skill overrides are no longer supported. Move these rules into a separate project-local Skill such as ${root.replace(/\\/g, "/")}/skills/product_plan/SKILL.md, ${root.replace(/\\/g, "/")}/skills/uiux_design/SKILL.md or ${root.replace(/\\/g, "/")}/skills/development_engineer/SKILL.md. Deprecated files: ${deprecatedFiles.join(", ")}`);
|
|
277
279
|
}
|
|
278
|
-
const relativeRoot = path.join(root, "pjsdlc_managed", "override_skills").split(path.sep).join("/");
|
|
279
|
-
report.blocked.push(`${relativeRoot}: Skill overrides are no longer supported. Move these rules into a separate project-local Skill such as ${root.replace(/\\/g, "/")}/skills/product_plan/SKILL.md, ${root.replace(/\\/g, "/")}/skills/uiux_design/SKILL.md or ${root.replace(/\\/g, "/")}/skills/development_engineer/SKILL.md. Deprecated files: ${deprecatedFiles.join(", ")}`);
|
|
280
280
|
}
|
|
281
|
-
function
|
|
282
|
-
return
|
|
281
|
+
function skillOverrideRoots(projectRoot, root) {
|
|
282
|
+
return [
|
|
283
|
+
{
|
|
284
|
+
absolute: path.join(projectRoot, root, "ty-context-managed", "override_skills"),
|
|
285
|
+
relative: path.join(root, "ty-context-managed", "override_skills").split(path.sep).join("/")
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
absolute: path.join(projectRoot, root, "pjsdlc_managed", "override_skills"),
|
|
289
|
+
relative: path.join(root, "pjsdlc_managed", "override_skills").split(path.sep).join("/")
|
|
290
|
+
}
|
|
291
|
+
];
|
|
283
292
|
}
|
|
284
293
|
async function syncFile(source, destination, report, missingMode) {
|
|
285
294
|
if (!(await pathExists(source))) {
|
|
@@ -331,18 +340,11 @@ async function syncGithubWorkflow(source, destination, relativePath, report) {
|
|
|
331
340
|
report.skipped.push(`${relativePath}: customized`);
|
|
332
341
|
}
|
|
333
342
|
function workflowMarkerState(content) {
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
const hasStart = startIndex >= 0;
|
|
337
|
-
const hasEnd = endIndex >= 0;
|
|
338
|
-
if (!hasStart && !hasEnd) {
|
|
343
|
+
const found = findManagedBlock(content, GITHUB_WORKFLOW_BLOCK_MARKERS);
|
|
344
|
+
if (found.status === "missing") {
|
|
339
345
|
return "missing";
|
|
340
346
|
}
|
|
341
|
-
if (
|
|
342
|
-
return "invalid";
|
|
343
|
-
}
|
|
344
|
-
if (content.indexOf(GITHUB_WORKFLOW_BLOCK_START, startIndex + GITHUB_WORKFLOW_BLOCK_START.length) >= 0 ||
|
|
345
|
-
content.indexOf(GITHUB_WORKFLOW_BLOCK_END, endIndex + GITHUB_WORKFLOW_BLOCK_END.length) >= 0) {
|
|
347
|
+
if (found.status === "invalid") {
|
|
346
348
|
return "invalid";
|
|
347
349
|
}
|
|
348
350
|
return "managed";
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -3,9 +3,21 @@ export interface HarnessConfig {
|
|
|
3
3
|
package: string;
|
|
4
4
|
schema_version: string;
|
|
5
5
|
};
|
|
6
|
+
modularity?: HarnessModularityConfig;
|
|
6
7
|
managed_files: ManagedFile[];
|
|
7
8
|
never_overwrite: string[];
|
|
8
9
|
}
|
|
10
|
+
export interface HarnessModularityConfig {
|
|
11
|
+
limit?: number;
|
|
12
|
+
policy?: "scoped_waivers" | "strict_except_generated";
|
|
13
|
+
waivers?: ModularityWaiverConfig[];
|
|
14
|
+
}
|
|
15
|
+
export interface ModularityWaiverConfig {
|
|
16
|
+
path?: string;
|
|
17
|
+
category?: string;
|
|
18
|
+
reason?: string;
|
|
19
|
+
future_split_boundary?: string;
|
|
20
|
+
}
|
|
9
21
|
export interface ManagedFile {
|
|
10
22
|
path: string;
|
|
11
23
|
strategy: "merge-block" | "generated" | "generated-compat" | "managed" | "merge-with-local" | "create-if-missing";
|
package/dist/lib/validators.js
CHANGED
|
@@ -2,10 +2,12 @@ import path from "node:path";
|
|
|
2
2
|
import { readConfig } from "./config.js";
|
|
3
3
|
import { harnessPath, harnessRoot } from "./harness-root.js";
|
|
4
4
|
import { listFiles, pathExists, readText } from "./fs.js";
|
|
5
|
+
import { runModularityCheck } from "./modularity.js";
|
|
5
6
|
import { unsupportedSchemaMessage } from "./schema-guard.js";
|
|
6
7
|
const VALIDATORS = {
|
|
7
8
|
"validate-context": validateContext,
|
|
8
|
-
"validate-
|
|
9
|
+
"validate-code-modularity": validateCodeModularity,
|
|
10
|
+
"validate-harness": validateHarness
|
|
9
11
|
};
|
|
10
12
|
const GLOBAL_REQUIRED_SECTIONS = [
|
|
11
13
|
...sectionSpecs([
|
|
@@ -71,12 +73,43 @@ export async function runValidator(projectRoot, gate) {
|
|
|
71
73
|
return {
|
|
72
74
|
info: [],
|
|
73
75
|
errors: [
|
|
74
|
-
`unknown validator: ${gate}. Minimal Context Harness supports validate-context and validate-harness only.`
|
|
76
|
+
`unknown validator: ${gate}. Minimal Context Harness supports validate-context, validate-code-modularity and validate-harness only.`
|
|
75
77
|
]
|
|
76
78
|
};
|
|
77
79
|
}
|
|
78
80
|
return validator(projectRoot);
|
|
79
81
|
}
|
|
82
|
+
async function validateHarness(projectRoot) {
|
|
83
|
+
const contextReport = await validateContext(projectRoot);
|
|
84
|
+
const modularityReport = await validateCodeModularity(projectRoot);
|
|
85
|
+
return {
|
|
86
|
+
info: [...contextReport.info, ...modularityReport.info],
|
|
87
|
+
errors: [...contextReport.errors, ...modularityReport.errors]
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
async function validateCodeModularity(projectRoot) {
|
|
91
|
+
const report = await runModularityCheck(projectRoot, { touched: true });
|
|
92
|
+
const info = [
|
|
93
|
+
`code modularity audited=${report.files.length} warning=${report.warnings.length} waived=${report.waivedWarnings.length} limit=${report.limit}`
|
|
94
|
+
];
|
|
95
|
+
if (report.files.length === 0) {
|
|
96
|
+
info.push("No handwritten source files matched the selected scope.");
|
|
97
|
+
}
|
|
98
|
+
for (const file of report.files) {
|
|
99
|
+
const prefix = file.overLimit && file.waived ? "waived" : file.overLimit ? "over-limit" : "ok";
|
|
100
|
+
info.push(`${prefix}: ${file.relativePath} ${file.lines} lines`);
|
|
101
|
+
}
|
|
102
|
+
for (const waiver of report.waivedWarnings) {
|
|
103
|
+
info.push(`waived: ${waiver}`);
|
|
104
|
+
}
|
|
105
|
+
if (report.errors.length === 0 && report.warnings.length === 0) {
|
|
106
|
+
info.push("Code modularity validation passed");
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
info,
|
|
110
|
+
errors: [...report.errors, ...report.warnings]
|
|
111
|
+
};
|
|
112
|
+
}
|
|
80
113
|
async function validateContext(projectRoot) {
|
|
81
114
|
const info = [];
|
|
82
115
|
const errors = [];
|
|
@@ -127,7 +160,7 @@ async function validateContext(projectRoot) {
|
|
|
127
160
|
}
|
|
128
161
|
}
|
|
129
162
|
else if (schemaRequiresContextManifest(schemaVersion)) {
|
|
130
|
-
errors.push("project_context/context.toml is missing; run
|
|
163
|
+
errors.push("project_context/context.toml is missing; run ty-context upgrade to create the Schema v4 Context graph manifest");
|
|
131
164
|
}
|
|
132
165
|
const contextFiles = (await listFiles(projectContextRoot))
|
|
133
166
|
.filter((file) => file.endsWith(".md"))
|
|
@@ -210,7 +243,7 @@ async function validateContextManifest(projectRoot, manifest, manifestRoles, err
|
|
|
210
243
|
async function addManifestRole(projectRoot, roles, rawPath, role, source, errors) {
|
|
211
244
|
const relative = normalizeContextPath(rawPath);
|
|
212
245
|
if (looksLikeExportArtifact(relative)) {
|
|
213
|
-
errors.push(`project_context/context.toml ${source} must not reference temporary export artifact ${rawPath}; export artifacts belong in tmp/
|
|
246
|
+
errors.push(`project_context/context.toml ${source} must not reference temporary export artifact ${rawPath}; export artifacts belong in tmp/ty-context/context-exports/** and must not be registered as Context graph nodes or implementation-index`);
|
|
214
247
|
return;
|
|
215
248
|
}
|
|
216
249
|
if (!relative.startsWith("project_context/") || !relative.endsWith(".md")) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "project-tiny-context-harness",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.55",
|
|
4
4
|
"description": "Minimal project memory and validation harness for AI coding agents.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Seven128",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
10
|
"url": "git+https://github.com/Seven128/project-tiny-context-harness.git",
|
|
11
|
-
"directory": "packages/
|
|
11
|
+
"directory": "packages/ty-context"
|
|
12
12
|
},
|
|
13
13
|
"bugs": {
|
|
14
14
|
"url": "https://github.com/Seven128/project-tiny-context-harness/issues"
|
|
@@ -33,12 +33,12 @@
|
|
|
33
33
|
"developer-tools",
|
|
34
34
|
"developer-productivity",
|
|
35
35
|
"cli",
|
|
36
|
-
"
|
|
36
|
+
"ty-context",
|
|
37
37
|
"workflow"
|
|
38
38
|
],
|
|
39
39
|
"type": "module",
|
|
40
40
|
"bin": {
|
|
41
|
-
"
|
|
41
|
+
"ty-context": "dist/cli.js"
|
|
42
42
|
},
|
|
43
43
|
"files": [
|
|
44
44
|
"README.md",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"scripts": {
|
|
51
51
|
"build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json",
|
|
52
52
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
53
|
-
"test": "npm run build && node --test ../../tests/
|
|
53
|
+
"test": "npm run build && node --test ../../tests/ty-context/*.test.mjs",
|
|
54
54
|
"prepack": "npm run build"
|
|
55
55
|
},
|
|
56
56
|
"engines": {
|
package/source-mappings.yaml
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
source_mappings:
|
|
2
|
-
- source: ".codex/
|
|
3
|
-
target: "packages/
|
|
2
|
+
- source: ".codex/ty-context-managed/agents/AGENTS_CORE.md"
|
|
3
|
+
target: "packages/ty-context/assets/agents/AGENTS_CORE.md"
|
|
4
4
|
mode: "copy-file"
|
|
5
5
|
- source: "README.md"
|
|
6
|
-
target: "packages/
|
|
6
|
+
target: "packages/ty-context/assets/README.md"
|
|
7
7
|
mode: "copy-file"
|
|
8
8
|
- source: "README.zh-CN.md"
|
|
9
|
-
target: "packages/
|
|
9
|
+
target: "packages/ty-context/assets/README.zh-CN.md"
|
|
10
10
|
mode: "copy-file"
|
|
11
|
-
- source: ".codex/
|
|
12
|
-
target: "packages/
|
|
11
|
+
- source: ".codex/ty-context-managed/context_templates"
|
|
12
|
+
target: "packages/ty-context/assets/context_templates"
|
|
13
13
|
mode: "copy-tree"
|
|
14
|
-
- source: ".codex/
|
|
15
|
-
target: "packages/
|
|
14
|
+
- source: ".codex/ty-context-managed/skills"
|
|
15
|
+
target: "packages/ty-context/assets/skills"
|
|
16
16
|
mode: "copy-tree"
|
|
17
|
-
- source: ".codex/
|
|
18
|
-
target: "packages/
|
|
17
|
+
- source: ".codex/ty-context-managed/make/ty-context.mk"
|
|
18
|
+
target: "packages/ty-context/assets/make/ty-context.mk"
|
|
19
19
|
mode: "copy-file"
|
|
20
|
-
- source: ".codex/
|
|
21
|
-
target: "packages/
|
|
20
|
+
- source: ".codex/ty-context-managed/minimal_tools"
|
|
21
|
+
target: "packages/ty-context/assets/tools"
|
|
22
22
|
mode: "copy-tree"
|
|
23
23
|
- source: ".github/workflows/harness.yml"
|
|
24
|
-
target: "packages/
|
|
24
|
+
target: "packages/ty-context/assets/github/harness.yml"
|
|
25
25
|
mode: "copy-file"
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
PYTHON ?= python3
|
|
2
|
-
SDLC_HARNESS ?= $(if $(wildcard packages/sdlc-harness/dist/cli.js),node packages/sdlc-harness/dist/cli.js,npx --yes --package project-tiny-context-harness@latest sdlc-harness)
|
|
3
|
-
|
|
4
|
-
.PHONY: help sdlc-doctor sdlc-sync sdlc-upgrade sdlc-check-modularity validate-context validate-harness lint test-current-domain test-all build
|
|
5
|
-
|
|
6
|
-
help:
|
|
7
|
-
@echo "Minimal Context Harness commands"
|
|
8
|
-
@echo " make sdlc-doctor Diagnose Harness root, core package and schema version"
|
|
9
|
-
@echo " make sdlc-sync Refresh managed assets; refuses when upgrade migrations are pending"
|
|
10
|
-
@echo " make sdlc-upgrade Run safe upgrade migrations, sync managed assets and doctor"
|
|
11
|
-
@echo " make sdlc-check-modularity Warn on oversized touched handwritten source files"
|
|
12
|
-
@echo " make validate-context Check whether project_context/** supports context recovery"
|
|
13
|
-
@echo " make validate-harness Compatibility alias for validate-context"
|
|
14
|
-
@echo " make test-all Run the project regression suite after replacing this placeholder"
|
|
15
|
-
|
|
16
|
-
sdlc-doctor:
|
|
17
|
-
$(SDLC_HARNESS) doctor
|
|
18
|
-
|
|
19
|
-
sdlc-sync:
|
|
20
|
-
$(SDLC_HARNESS) sync
|
|
21
|
-
|
|
22
|
-
sdlc-upgrade:
|
|
23
|
-
$(SDLC_HARNESS) upgrade
|
|
24
|
-
|
|
25
|
-
sdlc-check-modularity:
|
|
26
|
-
$(SDLC_HARNESS) check-modularity --touched
|
|
27
|
-
|
|
28
|
-
validate-context:
|
|
29
|
-
$(SDLC_HARNESS) validate-context
|
|
30
|
-
|
|
31
|
-
validate-harness: validate-context
|
|
32
|
-
|
|
33
|
-
lint:
|
|
34
|
-
@echo "No project lint command configured yet. Replace this target with your stack-specific lint command."
|
|
35
|
-
|
|
36
|
-
test-current-domain:
|
|
37
|
-
@echo "No domain test command configured yet. Replace this target with focused tests for the current change."
|
|
38
|
-
|
|
39
|
-
test-all:
|
|
40
|
-
@echo "No full test command configured yet. Replace this target with the project regression suite."
|
|
41
|
-
|
|
42
|
-
build:
|
|
43
|
-
@echo "No build command configured yet. Replace this target with the project build/package command."
|