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.
Files changed (46) hide show
  1. package/README.md +122 -69
  2. package/assets/README.md +113 -62
  3. package/assets/README.zh-CN.md +8 -6
  4. package/assets/agents/AGENTS_CORE.md +17 -16
  5. package/assets/context_templates/product-surface-contract.md +60 -0
  6. package/assets/github/harness.yml +15 -11
  7. package/assets/make/ty-context.mk +48 -0
  8. package/assets/skills/context_development_engineer/SKILL.md +9 -6
  9. package/assets/skills/context_full_project_export/SKILL.md +13 -13
  10. package/assets/skills/context_harness_upgrade/SKILL.md +9 -9
  11. package/assets/skills/context_product_plan/SKILL.md +7 -4
  12. package/assets/skills/context_surface_contract/SKILL.md +168 -0
  13. package/assets/skills/context_uiux_design/SKILL.md +7 -4
  14. package/assets/skills/plan_acceptance_checklist_compiler/SKILL.md +427 -0
  15. package/assets/tools/validate_context.py +1 -1
  16. package/dist/cli.js +1 -1
  17. package/dist/commands/check-modularity.js +14 -6
  18. package/dist/commands/export-context.js +4 -4
  19. package/dist/commands/index.js +8 -5
  20. package/dist/commands/init.js +1 -1
  21. package/dist/commands/package-source.js +1 -1
  22. package/dist/commands/upgrade.js +1 -1
  23. package/dist/lib/config.js +7 -2
  24. package/dist/lib/constants.d.ts +1 -1
  25. package/dist/lib/constants.js +1 -1
  26. package/dist/lib/context-export.js +5 -5
  27. package/dist/lib/harness-root.d.ts +5 -0
  28. package/dist/lib/harness-root.js +32 -4
  29. package/dist/lib/legacy-managed-scan.d.ts +2 -0
  30. package/dist/lib/legacy-managed-scan.js +79 -0
  31. package/dist/lib/legacy-sdlc-migration.d.ts +2 -0
  32. package/dist/lib/legacy-sdlc-migration.js +189 -0
  33. package/dist/lib/managed-file.d.ts +18 -12
  34. package/dist/lib/managed-file.js +25 -14
  35. package/dist/lib/migrations.js +4 -2
  36. package/dist/lib/modularity.d.ts +9 -0
  37. package/dist/lib/modularity.js +132 -8
  38. package/dist/lib/package-json-config.js +3 -3
  39. package/dist/lib/paths.d.ts +2 -2
  40. package/dist/lib/paths.js +2 -2
  41. package/dist/lib/sync-engine.js +33 -31
  42. package/dist/lib/types.d.ts +12 -0
  43. package/dist/lib/validators.js +37 -4
  44. package/package.json +5 -5
  45. package/source-mappings.yaml +13 -13
  46. package/assets/make/sdlc-harness.mk +0 -43
@@ -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 limit = options.limit ?? 300;
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
- files.push({ relativePath, lines, overLimit: lines > limit });
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
- return { limit, files, warnings };
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
- const result = await execFileAsync("git", ["-C", projectRoot, "status", "--porcelain", "-z"], {
61
- encoding: "utf8",
62
- maxBuffer: GIT_MAX_BUFFER
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.sdlcHarness;
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.sdlcHarness;
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
- sdlcHarness: nextConfig
27
+ tyContext: nextConfig
28
28
  };
29
29
  return writeTextIfChanged(packagePath, `${JSON.stringify(next, null, 2)}\n`);
30
30
  }
@@ -1,5 +1,5 @@
1
- export declare const SOURCE_MAPPINGS_PATH = "packages/sdlc-harness/source-mappings.yaml";
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 = "sdlc-harness.config.json";
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/sdlc-harness/source-mappings.yaml";
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 = "sdlc-harness.config.json";
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
  }
@@ -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, "pjsdlc_managed", "templates")) {
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, "pjsdlc_managed", "context_templates")) {
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, "pjsdlc_managed", "policies")) {
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, "pjsdlc_managed", "make", "sdlc-harness.mk")) {
66
- await syncFile(packageAssetPath("make", "sdlc-harness.mk"), destination, report, "skip-if-missing");
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, "/")}/pjsdlc_managed/make/sdlc-harness.mk`;
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 = skillOverrideRoot(projectRoot, root);
268
- if (!(await pathExists(overrideRoot))) {
269
- return;
270
- }
271
- const deprecatedFiles = (await listFiles(overrideRoot))
272
- .filter((file) => path.basename(file) !== ".gitkeep")
273
- .map((file) => path.relative(overrideRoot, file).split(path.sep).join("/"))
274
- .sort();
275
- if (deprecatedFiles.length === 0) {
276
- return;
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 skillOverrideRoot(projectRoot, root) {
282
- return path.join(projectRoot, root, "pjsdlc_managed", "override_skills");
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 startIndex = content.indexOf(GITHUB_WORKFLOW_BLOCK_START);
335
- const endIndex = content.indexOf(GITHUB_WORKFLOW_BLOCK_END);
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 (hasStart !== hasEnd || endIndex < startIndex) {
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";
@@ -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";
@@ -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-harness": validateContext
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 sdlc-harness upgrade to create the Schema v4 Context graph manifest");
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/sdlc/context-exports/** and must not be registered as Context graph nodes or implementation-index`);
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.53",
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/sdlc-harness"
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
- "sdlc",
36
+ "ty-context",
37
37
  "workflow"
38
38
  ],
39
39
  "type": "module",
40
40
  "bin": {
41
- "sdlc-harness": "dist/cli.js"
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/sdlc-harness/*.test.mjs",
53
+ "test": "npm run build && node --test ../../tests/ty-context/*.test.mjs",
54
54
  "prepack": "npm run build"
55
55
  },
56
56
  "engines": {
@@ -1,25 +1,25 @@
1
1
  source_mappings:
2
- - source: ".codex/pjsdlc_managed/agents/AGENTS_CORE.md"
3
- target: "packages/sdlc-harness/assets/agents/AGENTS_CORE.md"
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/sdlc-harness/assets/README.md"
6
+ target: "packages/ty-context/assets/README.md"
7
7
  mode: "copy-file"
8
8
  - source: "README.zh-CN.md"
9
- target: "packages/sdlc-harness/assets/README.zh-CN.md"
9
+ target: "packages/ty-context/assets/README.zh-CN.md"
10
10
  mode: "copy-file"
11
- - source: ".codex/pjsdlc_managed/context_templates"
12
- target: "packages/sdlc-harness/assets/context_templates"
11
+ - source: ".codex/ty-context-managed/context_templates"
12
+ target: "packages/ty-context/assets/context_templates"
13
13
  mode: "copy-tree"
14
- - source: ".codex/pjsdlc_managed/skills"
15
- target: "packages/sdlc-harness/assets/skills"
14
+ - source: ".codex/ty-context-managed/skills"
15
+ target: "packages/ty-context/assets/skills"
16
16
  mode: "copy-tree"
17
- - source: ".codex/pjsdlc_managed/make/sdlc-harness.mk"
18
- target: "packages/sdlc-harness/assets/make/sdlc-harness.mk"
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/pjsdlc_managed/minimal_tools"
21
- target: "packages/sdlc-harness/assets/tools"
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/sdlc-harness/assets/github/harness.yml"
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."