Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
9f3c6dc
test(diagnostics): define strict host diagnostic contract
seonghobae Aug 12, 2026
52c1ae4
ci(diagnostics): verify contract TDD red-green cycle
seonghobae Aug 12, 2026
67c5ac8
feat(diagnostics): add strict writing diagnostic contract
seonghobae Aug 12, 2026
68c8665
test(diagnostics): apply stricter limit in its own assertion
seonghobae Aug 12, 2026
8b60c14
test(diagnostics): require root and framework-free exports
seonghobae Aug 12, 2026
dfc8a8d
ci(diagnostics): verify source export contract
seonghobae Aug 12, 2026
6a2cae5
feat(diagnostics): add framework-free source subpath
seonghobae Aug 12, 2026
bae0d55
feat(diagnostics): export host diagnostic contract
seonghobae Aug 12, 2026
ce38a45
ci(diagnostics): enforce exact contract coverage
seonghobae Aug 12, 2026
9bb0117
ci(diagnostics): report uncovered contract paths
seonghobae Aug 12, 2026
246614a
test(diagnostics): cover hostile structural boundaries
seonghobae Aug 12, 2026
a38d3d4
ci(diagnostics): exercise hostile contract boundaries
seonghobae Aug 12, 2026
1cf16ac
refactor(diagnostics): remove unreachable validation branches
seonghobae Aug 12, 2026
67b0e56
test(diagnostics): keep missing-field fixture type-safe
seonghobae Aug 12, 2026
7c8dd98
ci(diagnostics): run full package acceptance
seonghobae Aug 12, 2026
c0baa93
docs(adr): restore canonical quality contract
seonghobae Aug 12, 2026
fa05e84
Merge design/llm-writing-diagnostics into feat/writing-diagnostics-co…
seonghobae Aug 12, 2026
6697bfe
docs(adr): inherit strict diagnostics v1 decision
seonghobae Aug 12, 2026
ba37c4d
docs(plan): inherit strict diagnostics v1 errata
seonghobae Aug 12, 2026
322c838
Merge design/llm-writing-diagnostics into feat/writing-diagnostics-co…
seonghobae Aug 12, 2026
525400d
docs(diagnostics): reconcile contract with current design
seonghobae Aug 13, 2026
0706e0d
chore(diagnostics): inherit unique ADR numbering
seonghobae Aug 16, 2026
0bbd3f3
chore(diagnostics): inherit release workflow repair
seonghobae Aug 16, 2026
e80545e
merge(parent): synchronize writing diagnostics contract lane
seonghobae Aug 18, 2026
81fbd55
test(package): require writing diagnostics subpath publication
seonghobae Aug 25, 2026
b878721
test(diagnostics): keep package publication on package owner
seonghobae Aug 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions .github/workflows/writing-diagnostics-contract-tdd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
name: Writing Diagnostics Contract TDD

on:
push:
branches:
- feat/writing-diagnostics-contract
workflow_dispatch:

permissions:
contents: read

concurrency:
group: writing-diagnostics-contract-tdd-${{ github.ref }}
cancel-in-progress: true

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
focused-contract:
runs-on: ubuntu-24.04
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.sha }}
persist-credentials: false
- uses: pnpm/action-setup@0e279bb959325dab635dd2c09392533439d90093 # v6.0.8
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- name: Collect focused contract coverage
id: focused_coverage
continue-on-error: true
run: >-
pnpm exec vitest run
src/writingDiagnostics.test.ts
src/writingDiagnosticsBoundary.test.ts
src/writingDiagnosticsExports.test.ts
--coverage
--coverage.include=src/writingDiagnostics.ts
--coverage.reporter=text
--coverage.reporter=json
--coverage.reporter=json-summary
- name: Report and enforce exact contract coverage
run: |
node <<'NODE'
const { readFileSync } = require('node:fs');
const coverage = JSON.parse(readFileSync('coverage/coverage-final.json', 'utf8'));
const [filePath, fileCoverage] = Object.entries(coverage).find(
([candidate]) => candidate.endsWith('/src/writingDiagnostics.ts'),
) ?? [];
if (!filePath || !fileCoverage) {
console.error('::error file=src/writingDiagnostics.ts,line=1::Focused coverage record is missing.');
process.exit(1);
}

const missingStatementLines = [...new Set(
Object.entries(fileCoverage.s)
.filter(([, count]) => count === 0)
.map(([id]) => fileCoverage.statementMap[id].start.line),
)].sort((a, b) => a - b);
const missingFunctionLines = [...new Set(
Object.entries(fileCoverage.f)
.filter(([, count]) => count === 0)
.map(([id]) => fileCoverage.fnMap[id].decl.start.line),
)].sort((a, b) => a - b);
const missingBranches = [];
for (const [id, counts] of Object.entries(fileCoverage.b)) {
counts.forEach((count, index) => {
if (count === 0) {
const branch = fileCoverage.branchMap[id];
const location = branch.locations?.[index] ?? branch.loc;
missingBranches.push(`${location.start.line}:${index}`);
}
});
}

const total = (values) => Object.values(values).reduce(
(sum, value) => sum + (Array.isArray(value) ? value.length : 1),
0,
);
const covered = (values) => Object.values(values).reduce(
(sum, value) => sum + (Array.isArray(value)
? value.filter((count) => count > 0).length
: Number(value > 0)),
0,
);
const statementTotal = total(fileCoverage.s);
const statementCovered = covered(fileCoverage.s);
const functionTotal = total(fileCoverage.f);
const functionCovered = covered(fileCoverage.f);
const branchTotal = total(fileCoverage.b);
const branchCovered = covered(fileCoverage.b);
console.log(
`::notice file=src/writingDiagnostics.ts,line=1::` +
`Statements ${statementCovered}/${statementTotal}; ` +
`functions ${functionCovered}/${functionTotal}; ` +
`branches ${branchCovered}/${branchTotal}.`,
);

if (
missingStatementLines.length ||
missingFunctionLines.length ||
missingBranches.length ||
process.env.FOCUSED_COVERAGE_OUTCOME !== 'success'
) {
console.error(
`::error file=src/writingDiagnostics.ts,line=1::` +
`Missing statement lines: ${missingStatementLines.join(', ') || 'none'}; ` +
`missing function lines: ${missingFunctionLines.join(', ') || 'none'}; ` +
`missing branches line:index: ${missingBranches.join(', ') || 'none'}.`,
);
process.exit(1);
}
NODE
env:
FOCUSED_COVERAGE_OUTCOME: ${{ steps.focused_coverage.outcome }}
- name: Typecheck public source contracts
run: pnpm typecheck
- name: Run complete production coverage gate
run: pnpm coverage
- name: Build all package entrypoints
run: pnpm build
- name: Verify isolated package consumers
run: pnpm verify:package
- name: Build the demonstration application
run: pnpm build:demo
14 changes: 14 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,20 @@ export type {
TextPositionSelectorEvidenceErrorCode,
} from './textPositionSelectorEvidence.js';

// Host-owned, revision-scoped writing diagnostic contract.
export {
DEFAULT_WRITING_DIAGNOSTIC_LIMITS,
WritingDiagnosticError,
validateWritingDiagnostics,
} from './writingDiagnostics.js';
export type {
CwlWritingDiagnostic,
CwlWritingDiagnosticPriority,
CwlWritingDiagnosticProvenance,
WritingDiagnosticErrorCode,
WritingDiagnosticLimits,
} from './writingDiagnostics.js';

// Versioned, lossless persistence boundary.
export {
DEFAULT_DOCUMENT_ENVELOPE_LIMITS,
Expand Down
19 changes: 19 additions & 0 deletions src/writing-diagnostics/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Framework-independent public surface for host-supplied writing diagnostics.
*
* This subpath validates bounded revision-scoped diagnostic proposals. It does
* not import React, create editor instances, call models/providers or networks,
* persist authored content, infer language quality, or mutate a document.
*/
export {
DEFAULT_WRITING_DIAGNOSTIC_LIMITS,
WritingDiagnosticError,
validateWritingDiagnostics,
} from '../writingDiagnostics.js';
Comment on lines +8 to +12

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 New writing-diagnostics subpath not published in package exports

This framework-independent public surface has no matching ./writing-diagnostics entry in package.json exports, no vite build config, and no verify script, unlike every sibling subpath. It is never emitted to dist, so consumers importing @contextualwisdomlab/cwl-editor/writing-diagnostics hit a module-resolution failure. The exports test passes only because it resolves source, not the packed artifact.

Prompt for agents
The new public subpath src/writing-diagnostics/index.ts is not wired into the package the way every other subpath is. To make `@contextualwisdomlab/cwl-editor/writing-diagnostics` resolvable from the published package, mirror the existing text-position-selector subpath setup: (1) add a `./writing-diagnostics` entry to the `exports` map in package.json pointing at the built dist files (types + import + require); (2) add a vite build config (e.g. vite.writing-diagnostics.config.ts) that builds src/writing-diagnostics/index.ts as a React-free library entry and emits its d.ts, and add it to the `build` script; (3) add a packed-artifact verification script (like scripts/verify-text-position-selector-subpath-package.mjs) and reference it from `verify:package`, per the AGENTS.md rule to validate public package behavior from packed artifacts rather than source imports alone.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified against current #249 head b878721bf1c085612f8aea889081f0322e62a2ae and the existing downstream package owner #282 head 03e626e57c9b85d5f6cc9b616249b09bdf1669e2.

#249 is intentionally Task 1's source-level, React-free contract. Its executable export test is explicitly named writing diagnostic public source exports and imports ./writing-diagnostics/index.js from source. Package publication is not owned by this branch.

Existing Draft #282 (feat(diagnostics): publish framework-neutral package subpath) is the sole package-surface writer. On its exact current head, package.json already contains ./writing-diagnostics types/import/require exports, adds vite.writing-diagnostics.config.ts to build, and adds verify-writing-diagnostics-subpath-package.mjs to verify:package; that PR also owns the packed-package verification/build files. Moving those package files into #249 would create a competing writer and break the accepted dependency split.

Therefore the reported module-resolution problem is valid as a downstream package-publication requirement, but it is not a defect in #249's bounded source-contract scope. Package resolvability remains acceptance work on #282 and cannot be claimed from #249 alone.

export type {
CwlWritingDiagnostic,
CwlWritingDiagnosticPriority,
CwlWritingDiagnosticProvenance,
WritingDiagnosticErrorCode,
WritingDiagnosticLimits,
} from '../writingDiagnostics.js';
Loading