@@ -150,9 +150,14 @@ export function verificationCommands(project, explicitPackageManager) {
150150 } ;
151151
152152 if ( project . stack === 'python' ) {
153+ // python3 is the portable name (many systems no longer ship a bare `python`).
154+ // pytest exits 5 when it collects zero tests — harmless here, so don't let `set -e`
155+ // treat "no tests yet" as a failure. compileall's -x skips virtualenvs and build
156+ // artifacts so a syntax check doesn't choke on dependencies it shouldn't compile.
157+ const py = 'python3' ;
153158 return [
154- 'python -m pytest' ,
155- 'python -m compileall .'
159+ ` ${ py } -m pytest || [ $? -eq 5 ]` ,
160+ ` ${ py } -m compileall -q -x '(^|/)(\\.?venv|env|node_modules|build|dist|__pycache__)(/|$)' .`
156161 ] ;
157162 }
158163
@@ -224,17 +229,17 @@ export function scoreHarness(files) {
224229 const checks = {
225230 instructions : [
226231 hasFile ( byPath , [ 'AGENTS.md' , 'CLAUDE.md' ] , 'Agent instruction file exists' ) ,
227- textHas ( agents , [ 'Startup Workflow' , 'Before writing code' ] , 'Startup workflow documented' ) ,
228- textHas ( agents , [ 'Definition of Done' , 'done only when' ] , 'Definition of done documented' ) ,
229- textHas ( agents , [ 'Verification Commands' , './init.sh' , 'test' , 'verify' ] , 'Verification commands discoverable' ) ,
230- textHas ( agents , [ 'feature_list.json' , 'progress.md' ] , 'State artifacts routed from instructions' )
232+ structuredHas ( agents , [ 'Startup Workflow' , 'Before writing code' ] , 'Startup workflow documented' ) ,
233+ structuredHas ( agents , [ 'Definition of Done' , 'done only when' ] , 'Definition of done documented' ) ,
234+ structuredHas ( agents , [ 'Verification Commands' , './init.sh' , 'test' , 'verify' ] , 'Verification commands discoverable' ) ,
235+ structuredHas ( agents , [ 'feature_list.json' , 'progress.md' ] , 'State artifacts routed from instructions' )
231236 ] ,
232237 state : [
233238 hasFile ( byPath , [ 'feature_list.json' , 'feature-list.json' ] , 'Feature tracker exists' ) ,
234239 jsonFeatureList ( featureList , 'Feature tracker is valid and has feature fields' ) ,
235240 hasFile ( byPath , [ 'progress.md' ] , 'Progress log exists' ) ,
236- textHas ( progress , [ 'Current State' , 'What' , 'Next' ] , 'Progress log supports restart' ) ,
237- textHas ( handoff || progress , [ 'Blockers' , 'Files' , 'Next Session' ] , 'Handoff captures blockers/files/next step' )
241+ structuredHas ( progress , [ 'Current State' , 'What' , 'Next' ] , 'Progress log supports restart' ) ,
242+ structuredHas ( handoff || progress , [ 'Blockers' , 'Files' , 'Next Session' ] , 'Handoff captures blockers/files/next step' )
238243 ] ,
239244 verification : [
240245 hasFile ( byPath , [ 'init.sh' ] , 'Verification entrypoint exists' ) ,
@@ -244,17 +249,17 @@ export function scoreHarness(files) {
244249 textHas ( allText , [ 'Evidence' , 'Verification Evidence' , 'command and output' ] , 'Verification evidence is recorded' )
245250 ] ,
246251 scope : [
247- textHas ( agents , [ 'One feature at a time' , 'one-feature-at-a-time' ] , 'One-feature-at-a-time rule exists' ) ,
252+ structuredHas ( agents , [ 'One feature at a time' , 'one-feature-at-a-time' ] , 'One-feature-at-a-time rule exists' ) ,
248253 textHas ( featureList , [ 'dependencies' ] , 'Feature dependencies are tracked' ) ,
249254 textHas ( agents + featureList , [ 'status' ] , 'Feature status is explicit' ) ,
250- textHas ( agents , [ 'Stay in scope' , 'scope' ] , 'Scope boundary documented' ) ,
251- textHas ( agents , [ 'Definition of Done' ] , 'Completion gate limits scope closure' )
255+ structuredHas ( agents , [ 'Stay in scope' , 'scope' ] , 'Scope boundary documented' ) ,
256+ structuredHas ( agents , [ 'Definition of Done' ] , 'Completion gate limits scope closure' )
252257 ] ,
253258 lifecycle : [
254259 hasFile ( byPath , [ 'init.sh' ] , 'Startup script exists' ) ,
255- textHas ( agents , [ 'End of Session' , 'Before ending' ] , 'End-of-session procedure exists' ) ,
260+ structuredHas ( agents , [ 'End of Session' , 'Before ending' ] , 'End-of-session procedure exists' ) ,
256261 hasFile ( byPath , [ 'session-handoff.md' ] , 'Session handoff template exists' ) ,
257- textHas ( progress + handoff , [ 'Last Updated' , 'Current Objective' , 'Recommended Next Step' ] , 'Session restart markers exist' ) ,
262+ structuredHas ( progress + '\n' + handoff , [ 'Last Updated' , 'Current Objective' , 'Recommended Next Step' ] , 'Session restart markers exist' ) ,
258263 textHas ( agents + init , [ 'restartable' , 'clean' , 'Next steps' ] , 'Clean restart path documented' )
259264 ]
260265 } ;
@@ -272,7 +277,10 @@ export function scoreHarness(files) {
272277
273278 const total = Object . values ( subsystems ) . reduce ( ( sum , item ) => sum + item . score , 0 ) ;
274279 const overall = Math . round ( ( total / ( SUBSYSTEMS . length * 5 ) ) * 100 ) ;
275- const bottleneck = Object . entries ( subsystems ) . sort ( ( a , b ) => a [ 1 ] . score - b [ 1 ] . score ) [ 0 ] [ 0 ] ;
280+ const ranked = Object . entries ( subsystems ) . sort ( ( a , b ) => a [ 1 ] . score - b [ 1 ] . score ) ;
281+ // A bottleneck only means something when a subsystem is weaker than the rest.
282+ // When every subsystem already maxes out, reporting one is misleading.
283+ const bottleneck = ranked [ 0 ] [ 1 ] . score === 5 ? null : ranked [ 0 ] [ 0 ] ;
276284 return { overall, bottleneck, subsystems } ;
277285}
278286
@@ -285,6 +293,31 @@ function textHas(text, needles, message) {
285293 return { pass : needles . some ( ( needle ) => lower . includes ( needle . toLowerCase ( ) ) ) , message } ;
286294}
287295
296+ // A real instruction doc carries its load-bearing phrases in structure — headings,
297+ // list items, tables, fenced code, or bold lead-ins — not in free prose. Scoring only
298+ // the structured lines means a genuine harness still passes, while a file that just
299+ // sprinkles the right keywords across a paragraph to game the score does not.
300+ function structuredText ( markdown ) {
301+ const kept = [ ] ;
302+ let inFence = false ;
303+ for ( const raw of markdown . split ( / \r ? \n / ) ) {
304+ const line = raw . trim ( ) ;
305+ if ( / ^ ( ` ` ` | ~ ~ ~ ) / . test ( line ) ) { inFence = ! inFence ; continue ; }
306+ if ( inFence ) { kept . push ( line ) ; continue ; }
307+ if ( ! line ) continue ;
308+ const isHeading = / ^ # { 1 , 6 } \s / . test ( line ) ;
309+ const isList = / ^ ( [ - * + ] | \d + \. ) \s / . test ( line ) ;
310+ const isTable = line . startsWith ( '|' ) ;
311+ const isBoldLead = / ^ \* \* [ ^ * ] + \* \* / . test ( line ) ;
312+ if ( isHeading || isList || isTable || isBoldLead ) kept . push ( line ) ;
313+ }
314+ return kept . join ( '\n' ) ;
315+ }
316+
317+ function structuredHas ( markdown , needles , message ) {
318+ return textHas ( structuredText ( markdown ) , needles , message ) ;
319+ }
320+
288321function jsonFeatureList ( text , message ) {
289322 try {
290323 const parsed = JSON . parse ( text ) ;
@@ -324,7 +357,7 @@ export function formatScoreReport(result, root = '.') {
324357 const lines = [
325358 `Harness validation for ${ root } ` ,
326359 `Overall: ${ result . overall } /100` ,
327- `Bottleneck: ${ result . bottleneck } ` ,
360+ `Bottleneck: ${ result . bottleneck ?? 'none — all subsystems at full score' } ` ,
328361 ''
329362 ] ;
330363
@@ -378,7 +411,7 @@ export function htmlReport(result, title = 'Harness Assessment') {
378411 <p>Five-subsystem harness validation report.</p>
379412 <div class="summary">
380413 <div class="metric">Overall<strong>${ result . overall } /100</strong></div>
381- <div class="metric">Bottleneck<strong>${ escapeHtml ( result . bottleneck ) } </strong></div>
414+ <div class="metric">Bottleneck<strong>${ escapeHtml ( result . bottleneck ?? 'none' ) } </strong></div>
382415 </div>
383416 </header>
384417 ${ rows }
0 commit comments