project-tiny-context-harness 0.2.74 → 0.2.75

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.
@@ -5,6 +5,12 @@ export async function validate(args) {
5
5
  for (const line of report.info) {
6
6
  console.log(line);
7
7
  }
8
+ for (const warning of report.warnings ?? []) {
9
+ console.error(`warning: ${warning}`);
10
+ }
11
+ for (const hygiene of report.hygiene ?? []) {
12
+ console.error(`hygiene: ${hygiene}`);
13
+ }
8
14
  for (const error of report.errors) {
9
15
  console.error(`error: ${error}`);
10
16
  }
@@ -0,0 +1 @@
1
+ export declare function validateAcceptanceArtifactDiagnostics(projectRoot: string, targetDir: string, matrixRows: Record<string, unknown>[], verdictRows: Record<string, unknown>[], verdictOverall: string, errors: string[], warnings: string[], hygiene: string[]): Promise<void>;
@@ -0,0 +1,220 @@
1
+ import { promises as fs } from "node:fs";
2
+ import path from "node:path";
3
+ import { readText } from "./fs.js";
4
+ import { findSensitiveEvidence } from "./plan-acceptance-evidence.js";
5
+ import { isOutOfScope, statusOf } from "./plan-acceptance-json.js";
6
+ import { isBlankish, primitiveText, repoRelative, valuesAsArray } from "./plan-validator-common.js";
7
+ const MILESTONE_STATUSES = new Set([
8
+ "not_started",
9
+ "implemented_no_proof",
10
+ "proof_partial",
11
+ "proof_ready",
12
+ "complete",
13
+ "blocked",
14
+ "out_of_scope_NA"
15
+ ]);
16
+ const GENERATED_ACTIVE_COUNTS = /<!--\s*generated:active-counts:start\s*-->([\s\S]*?)<!--\s*generated:active-counts:end\s*-->/i;
17
+ export async function validateAcceptanceArtifactDiagnostics(projectRoot, targetDir, matrixRows, verdictRows, verdictOverall, errors, warnings, hygiene) {
18
+ validateRows(matrixRows, "plan-conformance matrix row", warnings, hygiene);
19
+ validateRows(verdictRows, "final acceptance verdict row", warnings, hygiene);
20
+ const manifestEvidenceIds = await validateEvidenceManifest(projectRoot, targetDir, errors, warnings);
21
+ if (manifestEvidenceIds.size > 0) {
22
+ validateEvidenceIdReferences(matrixRows, "plan-conformance matrix row", manifestEvidenceIds, errors);
23
+ validateEvidenceIdReferences(verdictRows, "final acceptance verdict row", manifestEvidenceIds, errors);
24
+ }
25
+ await validateFinalVerdictMarkdown(projectRoot, targetDir, verdictRows, verdictOverall, errors, warnings, hygiene);
26
+ }
27
+ function validateRows(rows, labelPrefix, warnings, hygiene) {
28
+ for (const [index, row] of rows.entries()) {
29
+ const label = `${labelPrefix} ${index + 1}`;
30
+ validateMilestones(row, label, warnings);
31
+ validateStalePartialText(row, label, hygiene);
32
+ }
33
+ }
34
+ function validateMilestones(row, label, warnings) {
35
+ const milestones = row.milestones ?? row.proof_layer_milestones ?? row.proofLayerMilestones;
36
+ if (isBlankish(milestones)) {
37
+ return;
38
+ }
39
+ for (const status of extractMilestoneStatuses(milestones)) {
40
+ if (!MILESTONE_STATUSES.has(status)) {
41
+ warnings.push(`${label} has unsupported milestone status: ${status || "<empty>"}`);
42
+ }
43
+ }
44
+ }
45
+ function extractMilestoneStatuses(value) {
46
+ if (Array.isArray(value)) {
47
+ return value.flatMap(extractMilestoneStatuses);
48
+ }
49
+ if (!value || typeof value !== "object") {
50
+ return valuesAsArray(value).map((item) => item.trim());
51
+ }
52
+ const object = value;
53
+ if (!isBlankish(object.status)) {
54
+ return [String(object.status).trim()];
55
+ }
56
+ return Object.values(object).flatMap(extractMilestoneStatuses);
57
+ }
58
+ function validateStalePartialText(row, label, hygiene) {
59
+ const status = statusOf(row);
60
+ if (status === "complete" || status === "out_of_scope_NA") {
61
+ return;
62
+ }
63
+ const text = primitiveText(row);
64
+ const hasCompletionClaim = /\b(accepted|complete|final passed|goal achieved|product_goal_complete=true)\b/i.test(text);
65
+ const hasStaleQualifier = /\b(old|stale|previously|prior|outdated|superseded|no longer current)\b/i.test(text);
66
+ if (hasCompletionClaim && hasStaleQualifier) {
67
+ hygiene.push(`${label} has stale or overclaim completion prose while status is ${status || "<empty>"}`);
68
+ }
69
+ }
70
+ async function validateEvidenceManifest(projectRoot, targetDir, errors, warnings) {
71
+ const manifestFile = await findFile(targetDir, "evidence-manifest", ".json");
72
+ const evidenceIds = new Set();
73
+ if (!manifestFile) {
74
+ return evidenceIds;
75
+ }
76
+ let parsed;
77
+ try {
78
+ parsed = JSON.parse(await readText(manifestFile));
79
+ }
80
+ catch (error) {
81
+ errors.push(`${repoRelative(projectRoot, manifestFile)} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
82
+ return evidenceIds;
83
+ }
84
+ const entries = findManifestEntries(parsed);
85
+ if (entries.length === 0) {
86
+ warnings.push(`${repoRelative(projectRoot, manifestFile)} has no evidence entries`);
87
+ return evidenceIds;
88
+ }
89
+ for (const [index, entry] of entries.entries()) {
90
+ const label = `evidence manifest entry ${index + 1}`;
91
+ const evidenceId = firstString(entry.evidence_id, entry.evidenceId, entry.id);
92
+ if (evidenceId) {
93
+ evidenceIds.add(evidenceId);
94
+ }
95
+ else {
96
+ errors.push(`${label} is missing evidence_id`);
97
+ }
98
+ warnIfBlank(entry, label, warnings, "slice_id", "sliceId");
99
+ warnIfBlank(entry, label, warnings, "slice_goal", "sliceGoal");
100
+ warnIfBlank(entry, label, warnings, "touched_plan_item_ids", "touched_plan_ids", "plan_item_ids");
101
+ warnIfBlank(entry, label, warnings, "touched_ac_ids", "acceptance_ids", "ac_ids");
102
+ warnIfBlank(entry, label, warnings, "missing_layer_classes");
103
+ warnIfBlank(entry, label, warnings, "proves");
104
+ warnIfBlank(entry, label, warnings, "explicitly_does_not_prove", "does_not_prove");
105
+ warnIfBlank(entry, label, warnings, "closed_missing_layers", "closed_layers");
106
+ warnIfBlank(entry, label, warnings, "remaining_missing_layers", "remaining_layers");
107
+ warnIfBlank(entry, label, warnings, "cleanup_status");
108
+ warnIfBlank(entry, label, warnings, "redaction_security_status", "security_redaction_status");
109
+ warnIfBlank(entry, label, warnings, "freshness");
110
+ const sensitiveEvidence = findSensitiveEvidence(primitiveText(entry));
111
+ if (sensitiveEvidence) {
112
+ errors.push(`${label} contains raw secret/token/cookie material: ${sensitiveEvidence}`);
113
+ }
114
+ if (entry.safe_to_sync_to_verdict === true && !isBlankish(entry.remaining_missing_layers ?? entry.remaining_layers)) {
115
+ warnings.push(`${label} is safe_to_sync_to_verdict but still has remaining missing layers`);
116
+ }
117
+ }
118
+ return evidenceIds;
119
+ }
120
+ function findManifestEntries(value) {
121
+ if (Array.isArray(value)) {
122
+ return value.filter(isRecord);
123
+ }
124
+ if (!isRecord(value)) {
125
+ return [];
126
+ }
127
+ for (const key of ["evidence", "entries", "items", "manifest"]) {
128
+ const rows = findManifestEntries(value[key]);
129
+ if (rows.length > 0) {
130
+ return rows;
131
+ }
132
+ }
133
+ return [];
134
+ }
135
+ function validateEvidenceIdReferences(rows, labelPrefix, evidenceIds, errors) {
136
+ for (const [index, row] of rows.entries()) {
137
+ for (const evidenceId of valuesAsArray(row.evidence_id ?? row.evidence_ids ?? row.evidenceId ?? row.evidenceIds)) {
138
+ if (!evidenceIds.has(evidenceId)) {
139
+ errors.push(`${labelPrefix} ${index + 1} references unknown evidence_id: ${evidenceId}`);
140
+ }
141
+ }
142
+ }
143
+ }
144
+ async function validateFinalVerdictMarkdown(projectRoot, targetDir, verdictRows, verdictOverall, errors, warnings, hygiene) {
145
+ const markdownFile = await findFile(targetDir, "final-acceptance-verdict", ".md");
146
+ if (!markdownFile) {
147
+ return;
148
+ }
149
+ const content = await readText(markdownFile);
150
+ const match = GENERATED_ACTIVE_COUNTS.exec(content);
151
+ if (!match) {
152
+ if (/\b(active[-_ ]?count|complete_count|acceptance_required_count|missing_layer_count)\b/i.test(content)) {
153
+ hygiene.push(`${repoRelative(projectRoot, markdownFile)} has active-count-like prose outside generated active-count markers`);
154
+ }
155
+ return;
156
+ }
157
+ const declared = parseCountBlock(match[1]);
158
+ const actual = activeCounts(verdictRows);
159
+ for (const [key, expected] of Object.entries(actual)) {
160
+ const declaredValue = declared[key];
161
+ if (declaredValue === undefined) {
162
+ warnings.push(`${repoRelative(projectRoot, markdownFile)} generated active-count block is missing ${key}`);
163
+ continue;
164
+ }
165
+ if (declaredValue !== expected) {
166
+ const message = `${repoRelative(projectRoot, markdownFile)} generated active-count ${key}=${declaredValue} but current verdict has ${expected}`;
167
+ if (verdictOverall === "complete") {
168
+ errors.push(message);
169
+ }
170
+ else {
171
+ warnings.push(message);
172
+ }
173
+ }
174
+ }
175
+ }
176
+ function parseCountBlock(content) {
177
+ const counts = {};
178
+ for (const line of content.split(/\r?\n/)) {
179
+ const match = /^\s*([A-Za-z0-9_-]+)\s*[:=]\s*(\d+)\s*$/.exec(line);
180
+ if (match) {
181
+ counts[match[1].replace(/-/g, "_")] = Number(match[2]);
182
+ }
183
+ }
184
+ return counts;
185
+ }
186
+ function activeCounts(rows) {
187
+ return {
188
+ complete_count: rows.filter((row) => statusOf(row) === "complete").length,
189
+ partial_count: rows.filter((row) => statusOf(row) === "partial").length,
190
+ acceptance_required_count: rows.filter((row) => !isOutOfScope(row)).length,
191
+ missing_layer_count: rows.reduce((sum, row) => sum + missingLayerCount(row), 0)
192
+ };
193
+ }
194
+ function missingLayerCount(row) {
195
+ return valuesAsArray(row.missing_required_layers ?? row.missingRequiredLayers ?? row.missing_proof_layers ?? row.missing_evidence).length;
196
+ }
197
+ async function findFile(targetDir, marker, extension) {
198
+ const entries = await fs.readdir(targetDir, { withFileTypes: true });
199
+ return entries
200
+ .filter((entry) => entry.isFile() && entry.name.endsWith(extension) && entry.name.includes(marker))
201
+ .map((entry) => path.join(targetDir, entry.name))
202
+ .sort()[0];
203
+ }
204
+ function warnIfBlank(entry, label, warnings, ...keys) {
205
+ if (keys.some((key) => !isBlankish(entry[key]))) {
206
+ return;
207
+ }
208
+ warnings.push(`${label} is missing ${keys[0]}`);
209
+ }
210
+ function firstString(...values) {
211
+ for (const value of values) {
212
+ if (!isBlankish(value)) {
213
+ return String(value).trim();
214
+ }
215
+ }
216
+ return undefined;
217
+ }
218
+ function isRecord(value) {
219
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
220
+ }
@@ -1 +1,2 @@
1
1
  export declare function assertExternalReviewerFields(label: string, row: Record<string, unknown>, evidenceText: string, errors: string[]): void;
2
+ export declare function findSensitiveEvidence(text: string): string | undefined;
@@ -1,6 +1,20 @@
1
1
  import { isOutOfScope } from "./plan-acceptance-json.js";
2
2
  import { isBlankish, primitiveText, valuesAsArray } from "./plan-validator-common.js";
3
3
  const BLOCKING_AUDITOR_STATUSES = new Set(["partial", "blocked", "invalidated"]);
4
+ const SENSITIVE_EVIDENCE_PATTERNS = [
5
+ {
6
+ label: "Authorization bearer token",
7
+ pattern: /\bauthorization\s*:\s*bearer\s+(?!<redacted>|redacted|\[redacted\])[A-Za-z0-9._~+/=-]{8,}/i
8
+ },
9
+ {
10
+ label: "cookie value",
11
+ pattern: /\bcookie\s*[:=]\s*(?!<redacted>|redacted|\[redacted\])[^;\s]{8,}/i
12
+ },
13
+ {
14
+ label: "secret assignment",
15
+ pattern: /\b(?:api[_-]?key|access[_-]?token|refresh[_-]?token|token|secret|password)\s*[:=]\s*["']?(?!<redacted>|redacted|\[redacted\])[A-Za-z0-9_./+=-]{8,}/i
16
+ }
17
+ ];
4
18
  export function assertExternalReviewerFields(label, row, evidenceText, errors) {
5
19
  if (hasUnresolvedMissingRequiredLayers(row)) {
6
20
  errors.push(`${label} is complete but missing_required_layers is not empty`);
@@ -12,6 +26,9 @@ export function assertExternalReviewerFields(label, row, evidenceText, errors) {
12
26
  if (siblingSubstitutionUsed(row) && !hasSiblingSubstitutionApproval(row)) {
13
27
  errors.push(`${label} is complete but sibling_substitution_used without approval`);
14
28
  }
29
+ if (liveProofSubstitutionUsed(row) && !hasLiveProofSubstitutionApproval(row)) {
30
+ errors.push(`${label} is complete but live_proof_substitution_used without approval`);
31
+ }
15
32
  const auditorStatus = String(row.auditor_status ?? row.auditorStatus ?? "").trim().toLowerCase();
16
33
  if (BLOCKING_AUDITOR_STATUSES.has(auditorStatus)) {
17
34
  errors.push(`${label} is complete but auditor_status is ${auditorStatus}`);
@@ -19,6 +36,13 @@ export function assertExternalReviewerFields(label, row, evidenceText, errors) {
19
36
  if (hasOnlySelfCertifyingEvidence(evidenceText)) {
20
37
  errors.push(`${label} is complete but fresh_evidence contains only summary or self-certifying evidence`);
21
38
  }
39
+ const sensitiveEvidence = findSensitiveEvidence(evidenceText);
40
+ if (sensitiveEvidence) {
41
+ errors.push(`${label} is complete but evidence contains raw secret/token/cookie material: ${sensitiveEvidence}`);
42
+ }
43
+ }
44
+ export function findSensitiveEvidence(text) {
45
+ return SENSITIVE_EVIDENCE_PATTERNS.find((item) => item.pattern.test(text))?.label;
22
46
  }
23
47
  function hasUnresolvedMissingRequiredLayers(row) {
24
48
  const value = row.missing_required_layers ?? row.missingRequiredLayers ?? row.missing_proof_layers;
@@ -59,6 +83,22 @@ function hasSiblingSubstitutionApproval(row) {
59
83
  !isBlankish(row.substitution_approval_source) ||
60
84
  /\b(approved|explicitly allowed|out[_ -]?of[_ -]?scope[_ -]?NA)\b/i.test(primitiveText([row.sibling_substitution_approved, row.substitution_approval])));
61
85
  }
86
+ function liveProofSubstitutionUsed(row) {
87
+ const value = row.live_proof_substitution_used ?? row.liveProofSubstitutionUsed;
88
+ if (value === true) {
89
+ return true;
90
+ }
91
+ const text = primitiveText(value);
92
+ if (/\b(false|no|none|not used|not_used)\b/i.test(text)) {
93
+ return false;
94
+ }
95
+ return /\b(true|yes|used|present|substituted)\b/i.test(text);
96
+ }
97
+ function hasLiveProofSubstitutionApproval(row) {
98
+ return (!isBlankish(row.live_proof_substitution_approval_source) ||
99
+ !isBlankish(row.liveProofSubstitutionApprovalSource) ||
100
+ /\b(approved|explicitly allowed|out[_ -]?of[_ -]?scope[_ -]?NA)\b/i.test(primitiveText([row.live_proof_substitution_approved, row.liveProofSubstitutionApproved])));
101
+ }
62
102
  function hasOnlySelfCertifyingEvidence(text) {
63
103
  const entries = valuesAsArray(text);
64
104
  return entries.length > 0 && entries.every(isSelfCertifyingEvidence);
@@ -107,10 +107,14 @@ export function assertSurfaceConformance(row, label, errors) {
107
107
  errors.push(`${label} declares forbidden_primary_surfaces but negative_surface_checks is empty`);
108
108
  }
109
109
  const userPathText = primitiveText([row.required_user_paths, row.primary_user_paths]);
110
+ const evidenceText = primitiveText([row.real_page_evidence, row.user_path_evidence, row.fresh_evidence, row.runtime_evidence, row.artifact_evidence]);
110
111
  for (const forbiddenSurface of valuesAsArray(row.forbidden_primary_surfaces)) {
111
112
  if (userPathText.toLowerCase().includes(forbiddenSurface.toLowerCase())) {
112
113
  errors.push(`${label} routes a required/primary user path through forbidden surface: ${forbiddenSurface}`);
113
114
  }
115
+ if (evidenceText.toLowerCase().includes(forbiddenSurface.toLowerCase())) {
116
+ errors.push(`${label} uses wrong owner surface evidence from forbidden surface: ${forbiddenSurface}`);
117
+ }
114
118
  }
115
119
  if (row.default_visibility_required === true && !mentionsDefaultVisibility(primitiveText(row.real_page_evidence))) {
116
120
  errors.push(`${label} requires default visibility but real_page_evidence does not record default-visible proof`);
@@ -1,13 +1,16 @@
1
1
  import { pathExists } from "./fs.js";
2
+ import { validateAcceptanceArtifactDiagnostics } from "./plan-acceptance-artifacts.js";
2
3
  import { assertExternalReviewerFields } from "./plan-acceptance-evidence.js";
3
4
  import { AC_STATUSES, MATRIX_STATUSES, NON_COMPLETE_AC, NON_COMPLETE_MATRIX, assertStructuredNa, assertSurfaceConformance, contextDeltaRequired, findJsonFile, findRows, hasExplicitNoTestScope, isOutOfScope, isSurfaceConformanceRow, overallStatus, readJson, statusOf } from "./plan-acceptance-json.js";
4
5
  import { assertReferencedPathsExist, hasRealPageEvidence, isBlankish, isUiFacing, primitiveText, repoRelative, resolveInputDir, valuesAsArray, weakProofHit } from "./plan-validator-common.js";
5
6
  export async function validatePlanAcceptance(projectRoot, args = []) {
6
7
  const info = [];
8
+ const warnings = [];
9
+ const hygiene = [];
7
10
  const errors = [];
8
11
  const targetDir = await resolveInputDir(projectRoot, args[0], "tmp/ty-context/plan-acceptance");
9
12
  if (!(await pathExists(targetDir))) {
10
- return { info, errors: [`plan acceptance directory is missing: ${repoRelative(projectRoot, targetDir)}`] };
13
+ return { info, warnings, hygiene, errors: [`plan acceptance directory is missing: ${repoRelative(projectRoot, targetDir)}`] };
11
14
  }
12
15
  const matrixFile = await findJsonFile(targetDir, "plan-conformance-matrix");
13
16
  const verdictFile = await findJsonFile(targetDir, "final-acceptance-verdict");
@@ -18,12 +21,12 @@ export async function validatePlanAcceptance(projectRoot, args = []) {
18
21
  errors.push(`plan acceptance directory is missing *-final-acceptance-verdict.json`);
19
22
  }
20
23
  if (!matrixFile || !verdictFile) {
21
- return { info, errors };
24
+ return { info, warnings, hygiene, errors };
22
25
  }
23
26
  const matrix = await readJson(matrixFile, errors);
24
27
  const verdict = await readJson(verdictFile, errors);
25
28
  if (matrix === undefined || verdict === undefined) {
26
- return { info, errors };
29
+ return { info, warnings, hygiene, errors };
27
30
  }
28
31
  const matrixRows = findRows(matrix, ["plan_items", "items", "matrix", "entries", "plan_conformance"]);
29
32
  const verdictRows = findRows(verdict, ["acceptance_items", "ac_verdicts", "verdicts", "items", "entries", "acs"]);
@@ -31,11 +34,12 @@ export async function validatePlanAcceptance(projectRoot, args = []) {
31
34
  await validateVerdictRows(projectRoot, verdictRows, overallStatus(verdict), errors);
32
35
  validateCrossReferences(matrixRows, verdictRows, errors);
33
36
  validateContextFactReferences(matrix, verdict, matrixRows, verdictRows, errors);
37
+ await validateAcceptanceArtifactDiagnostics(projectRoot, targetDir, matrixRows, verdictRows, overallStatus(verdict), errors, warnings, hygiene);
34
38
  info.push(`checked plan acceptance ${repoRelative(projectRoot, targetDir)} matrix_rows=${matrixRows.length} verdict_rows=${verdictRows.length}`);
35
39
  if (errors.length === 0) {
36
40
  info.push("Plan acceptance artifact consistency passed");
37
41
  }
38
- return { info, errors };
42
+ return { info, warnings, hygiene, errors };
39
43
  }
40
44
  async function validateMatrixRows(projectRoot, rows, overall, errors) {
41
45
  if (rows.length === 0) {
@@ -1,5 +1,7 @@
1
1
  export interface ValidatorReport {
2
2
  info: string[];
3
+ warnings?: string[];
4
+ hygiene?: string[];
3
5
  errors: string[];
4
6
  }
5
7
  export declare function runValidator(projectRoot: string, gate: string, args?: string[]): Promise<ValidatorReport>;
@@ -88,6 +88,8 @@ async function validateHarness(projectRoot) {
88
88
  const modularityReport = await validateCodeModularity(projectRoot);
89
89
  return {
90
90
  info: [...contextReport.info, ...modularityReport.info],
91
+ warnings: [...(contextReport.warnings ?? []), ...(modularityReport.warnings ?? [])],
92
+ hygiene: [...(contextReport.hygiene ?? []), ...(modularityReport.hygiene ?? [])],
91
93
  errors: [...contextReport.errors, ...modularityReport.errors]
92
94
  };
93
95
  }
package/package.json CHANGED
@@ -1,69 +1,69 @@
1
- {
2
- "name": "project-tiny-context-harness",
3
- "version": "0.2.74",
4
- "description": "Minimal project memory and validation harness for AI coding agents.",
5
- "license": "MIT",
6
- "author": "Seven128",
7
- "homepage": "https://github.com/Seven128/project-tiny-context-harness#readme",
8
- "repository": {
9
- "type": "git",
10
- "url": "git+https://github.com/Seven128/project-tiny-context-harness.git",
11
- "directory": "packages/ty-context"
12
- },
13
- "bugs": {
14
- "url": "https://github.com/Seven128/project-tiny-context-harness/issues"
15
- },
16
- "keywords": [
17
- "ai-agents",
18
- "coding-agent",
19
- "codex",
20
- "claude-code",
21
- "cursor",
22
- "gemini-cli",
23
- "opencode",
24
- "agent-context",
25
- "context-engineering",
26
- "context-management",
27
- "agents-md",
28
- "project-memory",
29
- "agent-memory",
30
- "ai-coding",
31
- "multi-agent",
32
- "llm",
33
- "developer-tools",
34
- "developer-productivity",
35
- "cli",
36
- "ty-context",
37
- "workflow"
38
- ],
39
- "type": "module",
40
- "bin": {
41
- "ty-context": "dist/cli.js"
42
- },
43
- "files": [
44
- "README.md",
45
- "dist",
46
- "assets",
47
- "migrations",
48
- "source-mappings.yaml"
49
- ],
50
- "scripts": {
51
- "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json",
52
- "typecheck": "tsc -p tsconfig.json --noEmit",
53
- "test:built": "node --test ../../tests/ty-context/*.test.mjs",
54
- "test": "npm run build && node --test ../../tests/ty-context/*.test.mjs",
55
- "prepack": "npm run build"
56
- },
57
- "engines": {
58
- "node": ">=20"
59
- },
60
- "dependencies": {
61
- "@google/design.md": "^0.2.0",
62
- "impeccable": "^2.3.2",
63
- "yaml": "^2.9.0"
64
- },
65
- "devDependencies": {
66
- "@types/node": "^24.0.0",
67
- "typescript": "^5.5.0"
68
- }
69
- }
1
+ {
2
+ "name": "project-tiny-context-harness",
3
+ "version": "0.2.75",
4
+ "description": "Minimal project memory and validation harness for AI coding agents.",
5
+ "license": "MIT",
6
+ "author": "Seven128",
7
+ "homepage": "https://github.com/Seven128/project-tiny-context-harness#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/Seven128/project-tiny-context-harness.git",
11
+ "directory": "packages/ty-context"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/Seven128/project-tiny-context-harness/issues"
15
+ },
16
+ "keywords": [
17
+ "ai-agents",
18
+ "coding-agent",
19
+ "codex",
20
+ "claude-code",
21
+ "cursor",
22
+ "gemini-cli",
23
+ "opencode",
24
+ "agent-context",
25
+ "context-engineering",
26
+ "context-management",
27
+ "agents-md",
28
+ "project-memory",
29
+ "agent-memory",
30
+ "ai-coding",
31
+ "multi-agent",
32
+ "llm",
33
+ "developer-tools",
34
+ "developer-productivity",
35
+ "cli",
36
+ "ty-context",
37
+ "workflow"
38
+ ],
39
+ "type": "module",
40
+ "bin": {
41
+ "ty-context": "dist/cli.js"
42
+ },
43
+ "files": [
44
+ "README.md",
45
+ "dist",
46
+ "assets",
47
+ "migrations",
48
+ "source-mappings.yaml"
49
+ ],
50
+ "scripts": {
51
+ "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json",
52
+ "typecheck": "tsc -p tsconfig.json --noEmit",
53
+ "test:built": "node --test ../../tests/ty-context/*.test.mjs",
54
+ "test": "npm run build && node --test ../../tests/ty-context/*.test.mjs",
55
+ "prepack": "npm run build"
56
+ },
57
+ "engines": {
58
+ "node": ">=20"
59
+ },
60
+ "dependencies": {
61
+ "@google/design.md": "^0.2.0",
62
+ "impeccable": "^2.3.2",
63
+ "yaml": "^2.9.0"
64
+ },
65
+ "devDependencies": {
66
+ "@types/node": "^24.0.0",
67
+ "typescript": "^5.5.0"
68
+ }
69
+ }