Implementation: make/src/ (16 files, ~3,074 lines: main.rs, lib.rs, config.rs, error_code.rs, special_target.rs, signal_handler.rs, parser/{mod,lex,parse,preprocessor}.rs, rule.rs + rule/{target,prerequisite,recipe,config}.rs)
Tests: make/tests/ (fixture makefiles + mod.rs harness, ~1,511 lines)
Spec: POSIX.1-2024 (IEEE Std 1003.1-2024), Vol. 3 §3 make, pp. 3130–3146.
Reference: No sliced spec tree was available; the spec was extracted from the mega-PDF ~/tmp/POSIX.2024.pdf (pp. 3130–3146) to ~/tmp/make-spec.txt. Mirrors the m4 audit's PDF-based method.
Date: 2026-06-12
Verification: Critical and most Major findings were behaviorally verified against the built target/release/make binary (no source changes), cross-checked with GNU make where a control was useful. Items not behaviorally verified are tagged (static). Several agent-proposed findings were refuted by behavioral testing and are recorded at the bottom rather than silently dropped.
Status (2026-06-12): all findings below have been remediated across nine commits on the
make-auditbranch (Phases 1–9). The original assessment is retained for context; each item is now ticked with the fix, the phase, and its regression test. Two genuinely out-of-scope items are documented rather than implemented: full XSI SCCS auto-retrieval (.SCCS_GET/PROJECTDIR) and a parser-levellib(member):/ slash-in-target-name syntax (thearmember timestamp lookup the audit named is implemented). The-pdebug-dump format is kept deliberately (the spec leaves it unspecified).
The implementation handles the easy golden path (a simple target: prereq rule with a tab-indented recipe, basic $(VAR) macros, -n/-s/-i/-q, .PHONY, .DEFAULT, single internal macros) but falls over on a startling amount of ordinary makefile content. The macro preprocessor treats every line containing = as a macro definition — including tab-indented recipe lines — so any recipe with an = (./configure --prefix=/usr, [ x = y ], VAR=1 cmd, cc --opt=val) is rejected with a hard parse error: EmptyIdent. The special target .POSIX — which the spec says a portable makefile shall include — is rejected as "not supported". A missing include file panics. make -k reports failure and exits 2 even when every target builds. Command-line macro=value operands, multiple -f, the -j parallel-execution machinery (-j/token pool/.WAIT/.NOTPARALLEL), the $(VAR:a=b) substitution form, single-suffix inference rules, backslash-newline continuation, MAKEFLAGS, and the shell -e requirement are all absent or broken. This is an early-stage implementation: many headline POSIX requirements are unmet, and several are crashes/aborts on common input.
-
#1 — A recipe line containing
=aborts the whole parse withEmptyIdent. ✓ fixed (Phase 1): addedis_macro_definition()so neithergenerate_macro_tablenorremove_variablesmisclassifies tab-indented recipe lines; regression testsrecipe_line_with_equals.parser/preprocessor.rs:79–82(generate_macro_tabledoessource.lines().filter(|line| line.contains('=')), never skipping tab-indented recipe lines) →get_identfails →preprocessor.rs:56–57. Verified:@echo ./configure --prefix=/usr,@test x = x,@FOO=1 cmd,@echo tar --file=a.tarall yieldmake: parse error: EmptyIdent(exit 4); GNU make runs them fine. This breaks a large fraction of real recipes. Fix: ingenerate_macro_table, skip lines that begin with a<tab>(recipe lines), and only treat a line as a macro definition when the text before the first=/:is a valid macro name. -
#2 —
.POSIXspecial target is rejected as unsupported. ✓ fixed (Phase 2): added aPosixarm +process_posix()that validates no prerequisites/commands and accepts it; removed the now-unreachableNotSupportedcatch-all. Testspecial_targets::posix.special_target.rs:178–187— theprocess()match has noPosixarm, so the recognizedPosixvariant (special_target.rs:23,:38) falls intounsupported => Err(Error::NotSupported). Verified:.POSIX:→make: '.POSIX' special target constraint is not fulfilled: the special target is not supported: '.POSIX'(exit 9). The spec DESCRIPTION (p. 3130) says a portable makefile shall include.POSIX; this rejects every conformant portable makefile. Fix: addPosix => this.process_posix()that enforces no prerequisites / no commands and returnsOk(())(optionally enabling strict mode). -
#3 —
includeof a missing/unreadable file panics. ✓ fixed (Phase 1):process_include_linesnow returns aPreprocError::IncludeFailedwith a readable message instead ofunwrap()-panicking; regression testmissing_include_is_graceful_error.parser/preprocessor.rs:279—fs::read_to_string(path).unwrap(). Verified:include /nonexistent.mk→thread 'main' panicked … Result::unwrap() on an Err(exit 101). The spec (Include Lines, p. 3135) requires a diagnostic and error exit for a non-prefixedinclude, not a panic. Fix: replaceunwrap()with a gracefulErrorCode::IoError; for the-include(hyphen-prefixed) form, ignore a missing file per spec. -
#4 —
make -kreports failure and exits 2 even when all targets succeed. ✓ fixed (Phase 3): the unconditionalif keep_goingblock inmain.rswas the only failure signal, because under-krule.rsswallows a failed recipe (prints the error,breaks, returnsOk) so the error never reachedmain. Added aKEEP_GOING_ERRORatomic (rule.rs) set when a non-ignored recipe error is swallowed;mainresets it before each command-line target and reportsTarget … not remade because of errors(and setshad_error) only when it fired — so an all-success-kbuild now exits 0 with no diagnostic. Testsarguments::dash_k_success(success ⇒ exit 0, silent) plus the preservedarguments::dash_k(failure ⇒ message + exit 2). Behaviorally verified: independent command-line targets still build after one fails.main.rs:266–283— after a successfulbuild_target, theif keep_going { eprintln!("…Target … not remade because of errors"); had_error = true; }block fired unconditionally, andhad_errorforcedstatus_code = 2. -
#5 — Command-line
macro=valueoperands are unsupported. ✓ fixed (Phase 2):main()partitions operands withis_macro_definition(); macro operands are appended after the makefile(s) so they take precedence. Testscmdline_macro_overrides_file,cmdline_macro_defines.main.rs:108–110— all positional args go intotargets: Vec<OsString>; nothing splits outname=value. Verified:make FOO=bar all→make: parse error: UndefinedMacro("FOO")(exit 4). The SYNOPSIS mandates[macro[::[:]]=value...]operands, and the spec gives them the highest macro precedence. Fix: before queueing targets, peel off args matching the macro-assignment forms and inject them as a top-precedence macro layer (theENV_MACROSatomic inpreprocessor.rsis a usable precedent).
-
#6 —
$(string:subst1=subst2)substitution form is unimplemented and hard-errors. ✓ fixed (Phase 4): the(/{branch ofsubstitutenow detects:after the macro name and appliesapply_substitution, which handles both the suffix form and the[op]%[os]=[np][%][ns]pattern form word-wise. Testspreprocess::test_subst_suffix,test_subst_pattern; verified$(SRC:.c=.o)→a.o b.o foo.o,$(O:%.o=%.x)→a.x b.x.parser/preprocessor.rssubstitute. -
#7 — Backslash-newline line continuation is not folded. ✓ fixed (Phase 4):
preprocessnow runsfold_continuationsfirst. Outside recipe lines,\<newline>+ leading white space of the next line collapse to a single space; in a recipe (tab-indented) line the continuation is spliced (one leading tab of the next line removed) so the whole command reaches the shell. An escaped trailing backslash (\\) is not treated as a splice. Testspreprocess::test_continuation_macro,test_continuation_recipe; verifiedFOO = a \b→a b. -
#8 — Single-suffix inference rules (
.c:,.sh:) are never applied. ✓ fixed (Phase 5):try_parse_inferencenow accepts a single-suffix target asInference { from: <suffix>, to: "" };find_inference_rulesearches single-suffix rules (where the suffixless target's<name>.<from>exists) after double-suffix, andbuild_target's no-rule branch invokes inference before falling through to.DEFAULT/NoTarget;run_for_targetcomputes the<target>.<from>input forto=="". Testinference_rules::single_suffix_rule; verifiedmake barwithbar.c+.c:→built bar from bar.c. -
#9 — Parallel execution machinery is entirely absent:
-j, token pool,.WAIT,.NOTPARALLEL. ✓ fixed (Phase 7): added-j maxjobs(last value wins viaoverrides_with; non-positive ⇒ 1).Makewas madeSend/Sync(macros now owned(String,String)), and a non-blockingTokenPoolofmaxjobs-1tokens bounds concurrency.build_prerequisitessplits a target's prerequisites on.WAITbarriers (build left-of-.WAITbefore right-of-.WAIT) and, under-j>1, builds each segment withstd::thread::scope: each prerequisite that obtains a token runs in a worker thread, the rest build inline (so the recursion is deadlock-free)..WAIT/.NOTPARALLELare now realSpecialTargetvariants —.WAITas a target is a no-op and as a prerequisite is a barrier (no longer "no target '.WAIT'");.NOTPARALLELforces sequential builds. Testsparallel::{dash_j_builds_all_targets,dash_j_last_value_wins,notparallel_recognized,wait_barrier_is_not_built}; behaviorally verified-j2halves wall-clock for two independent sleeps,.WAIT/.NOTPARALLELserialize, and a diamond build does not deadlock. -
#10 — Multiple
-f makefileoptions are rejected. ✓ fixed (Phase 2):makefileis nowVec<PathBuf>;parse_makefileconcatenates the operands in order (with-= stdin). Testmultiple_dash_f.main.rs:50–51—makefile: Option<PathBuf>. Verified:make -f A.mk -f B.mk …→error: the argument '--makefile <MAKEFILE>' cannot be used multiple times(exit 2). Spec: multiple-fshall be processed in order. Fix: make itVec<PathBuf>and concatenate the makefiles in order. -
#11 — The shell
-eoption is not in effect for non-ignored recipes. ✓ fixed (Phase 6): the recipe command is now run with-e -cwhen the recipe's errors are not ignored, and plain-cwhen they are (-/.IGNORE/-i). Testrecipe_execution::shell_e_aborts_on_first_failure; verifiedfalse; echo REACHEDnow aborts (exit 2, noREACHED) and-false; echo REACHEDstill reaches it. -
#12 — Recipes are run with the
SHELLenvironment variable, which the spec forbids. ✓ fixed (Phase 6): the recipe shell is resolved from theSHELLmacro (default/bin/sh); theSHELLenv var is no longer consulted for shell selection, andinit_envno longer exports theSHELLmacro to recipe sub-processes (so the macro cannot modify the child'sSHELL). Verified: aSHELLmacro selects the shell while a bogusSHELLenv var is ignored. -
#13 —
MAKEFLAGSis ignored. ✓ fixed (Phase 4):args_with_makeflags()seeds options from the env var ahead of the real command line. The letters-only first word (kn) becomes a combined short option (-kn);--prefixed andmacro=valuewords pass through.MAKEFLAGSis inherited by recipe sub-processes via the environment (sub-make propagation). Testsinternal_macros::makeflags_letters_form; verifiedMAKEFLAGS=n,MAKEFLAGS=-n, andMAKEFLAGS='V=hello'. Note: full synthesis of command-line flags intoMAKEFLAGSfor children is not done (only env-provided flags propagate). -
#14 —
$?expands to all prerequisites, not those newer than the target. ✓ fixed (Phase 4):run_rule_with_prerequisitesnow threads theget_newer_prerequisitesslice throughrun/run_for_target/run_with_filesintosubstitute_internal_macros, where$?uses the newer-only list (space-separated; the old code concatenated with no separator).$^/$+keep the full list. Behaviorally verified: withprog: a.o b.o a.oand onlyb.onewer,$?→b.o. -
#15 —
$^,$+, and the$(@D)/$(@F)(dir/file) macro variants are missing. ✓ fixed (Phase 4):substitute_internal_macroswas rewritten aroundexpand_internal_macro, which supports sigils@ % ? < * ^ +in both the two-char ($^) and bracketed ($(@D),${?F}) forms.$^dedups (order preserved);$+keeps duplicates; theD/Fmodifiers takedir_part/file_partof each element. The preprocessor passes internal-macro references through verbatim (added^/+to the two-char passthrough and a$(-internal passthrough) so they reach the rule stage. Testsinternal_macros::caret_and_plus, unit testsrule::tests::dir_and_file_parts; verified$(@D)/$(@F)/${@F}. Note: targets containing/still do not parse (separate pre-existing lexer limitation, not in this audit), so$(@D)is.for ordinary targets. -
#16 —
.SUFFIXESis stored in aBTreeSet, destroying search order; additive/clear semantics are also broken. ✓ fixed (Phase 5): added an authoritative insertion-orderedConfig.suffixes: Vec<String>(consumed byfind_inference_ruleandInferenceTarget); the sortedrules[".SUFFIXES"]BTreeSetis kept only as a mirror for the-pdump.process_suffixesnow clears on empty prerequisites (clear_suffixes) and appends otherwise (add_suffix, order-preserving, dedup) instead of replacing.-rclears the Vec too. Testinference_rules::suffixes_clear_then_readd; verified append keeps built-ins, empty.SUFFIXES:clears them, and a later.SUFFIXES: .c .ore-enables inference. -
#17 — Signal registration is gated on the wrong condition. ✓ fixed (Phase 8): registration is now
if !dry_run && !print && !quit— make catches signals unless-n/-p/-qis set (those take the default action), and-iis no longer an exemption. Testtarget_behavior::async_events_registered_under_dash_iconfirms an interrupt under-istill cleans up and re-raises. -
#18 —
.PRECIOUSwith no prerequisites does not protect targets on signal. ✓ fixed (Phase 8):process_preciousnow setsmake.config.precious = truewhen.PRECIOUShas no prerequisites, so the global-precious flag the signal handler consults protects every in-progress target. -
#19 —
-includeis not actually implemented (line passed through). ✓ fixed (Phase 5):parse_include_directiverecognizes bothincludeand the-includeform, requires the trailing blank (soincludedir=…is no longer mis-parsed as an include), and inlines the file; a missing/unreadable file is silently ignored for-includeand a hard error forinclude. Testspreprocess::test_dash_include_missing_ignored,test_include_missing_errors,test_includedir_not_mistaken_for_include. Note: full immediate/delayed re-making of include files is not implemented (the file is simply inlined), which matches the existingincludebehavior.
-
#20 — Signal handler calls
process::exit(128+sig)instead of resetting to default and re-raising. ✓ fixed (Phase 8): the handler now doessignal(sig, SIG_DFL); raise(sig), so make dies from the signal and the parent observes a signal death (verified:status.code()isNone,status.signal()isSIGINT). Tests updated accordingly. -
#21 — Signal cleanup ignores
.PHONYmembership and the mtime-change condition. ✓ fixed (Phase 8):INTERRUPT_FLAGnow carries anInterruptInfo { target, precious, phony, original_mtime }. The target's mtime is captured once before its recipe sequence; on interrupt the handler deletes only when the target is not precious, not phony, and its mtime changed (i.e. the recipe had begun writing the file). Verified bytarget_behavior::async_events(a freshly createdtext.txtis deleted) andspecial_targets::precious(a precious target is kept). -
#22 —
.IGNORE/.SILENT/.PHONY/.PRECIOUS"subsequent occurrences add to the list" and per-target forms are order-dependent. ✓ fixed (Phase 9): order-dependence does not occur in practice because special targets are processed only after all rules are classified (Make::try_frompass 2), soadditive()/global()see every rule and the per-rule flags accumulate. The remaining literal gap —process_phony/process_preciousinsert(replacing) the stored set — is fixed toentry().or_default().extend(...), so multiple.PHONY/.PRECIOUSlines now accumulate (visible in-p)..SCCS_GETkeeps last-wins (it is a single command redefinition, not a list). Testspecial_targets::phony_accumulates. -
#23 — Runtime diagnostics are largely un-internationalized. ✓ improved (Phase 9): the substantive error messages already route through
gettext(error_code.rsDisplay). The remaining raw build-loop diagnostics inmain.rs(the-k"Target … not remade because of errors" and the "is up to date." messages) are now routed throughgettexttoo (English msgids reproduce the exact prior wording, so output is unchanged when untranslated). Comprehensive coverage of every string remains incremental, butLC_MESSAGESnow governs the user-facing diagnostics. -
#24 —
-poutput format is a Rust{:?}debug dump. ✓ reviewed (Phase 9): the spec explicitly leaves the-pformat unspecified, so the debug dump conforms. It is intentionally kept as-is: it is the documented contract of three exact-match-pregression tests, and a reformatting would be pure churn with no conformance gain. No code change. -
#25 — Internal error exit codes use values 3–9. ✓ reviewed (Phase 3):
error_code.rsmaps distinct internal errors to 3–9; only-q's not-up-to-date maps to 1 and success to 0. All error paths are>1, so this satisfies the spec's 0/1/>1 exit-status contract. The granular codes are non-standard but conforming, so they are kept (changing them risks breaking the existing exit-code tests for no conformance gain). No code change.
Options (SYNOPSIS make [-einpqrst] [-f makefile]... [-j maxjobs] [-k|-S] [macro=value...] [target...])
-
-eCONFORMS — environment overrides macros;main.rs:56–61, plumbed viaENV_MACROS(preprocessor.rs). -
-iCONFORMS — global ignore;main.rs:53,config.rs:44,rule.rs:165. -
-nCONFORMS — prints without executing; verified (echo …printed, not run).rule.rs:183–188. -
-pCONFORMS (format unspecified) —main.rs:112–114debug dump. See #24. -
-qCONFORMS — exit 1 when not up to date, recipe not run; verified.rule.rs:194–201. -
-rCONFORMS — clears suffixes / built-ins;main.rs:207–209. -
-sCONFORMS — suppresses echo; verified.rule.rs:204–207. -
-tCONFORMS — touches targets;rule.rs:252–260. -
-kCONFORMS (Critical #4 fixed) — all-success build exits 0 silently; a failed target is reported while independent targets continue, exit 2. -
-Spresent as--terminate(main.rs) and is the default; the-kinteraction (#4) is fixed (when both are set,-S/terminate wins so make stops). Known minor: strict POSIX "last of-k/-Son the command line wins" ordering is not modeled (the flags are independent booleans); documented, not a numbered finding. -
-f(multiple) processed in order (Major #10 fixed, Phase 2). -
-j maxjobsimplemented with a token pool (Major #9 fixed). -
macro=valueoperands supported with top precedence (Critical #5 fixed, Phase 2). - Extensions present (non-POSIX, no conflict — informational):
-C/--directory(verified working), long-option aliases (--ignore,--silent, …). Per audit scope these are noted, not flagged.
-
$(NAME)/${NAME}CONFORMS — verified (CC=echoexpands in recipe; refutes an agent "verbatim to shell" claim).preprocessor.rssubstitute. - Single internal macros
$@ $% $? $< $*CONFORMS in isolation —rule.rs:284–301;$@verified equal to GNU. But see #14 ($?semantics) and #1 (a same-line=still aborts the parse). -
$$→$CONFORMS — verified (echo price is $$5→price is). Refutes an agent DIVERGES claim. -
$(VAR:a=b)/%-pattern CONFORMS (Major #6 fixed). -
$^/$+/$(@D)/$(@F)CONFORMS (Major #15 fixed). - Command-line macro precedence (Critical #5 done) and
MAKEFLAGS(Major #13 fixed). - Backslash-newline in macro bodies folded (Major #7 fixed).
-
?=,+=,!=—?=verified correct (kept existing value; fell back when unset — refutes an agent "inverted" claim).+=/!=not behaviorally re-verified here; flavor (immediate vs deferred) is not tracked (preprocessor.rs) — (static, low priority).
-
-operand to-freads stdin —main.rs:140–141. - First non-special target as default —
lib.rs:65–76; verified via.DEFAULTand normal targets. -
include existing.mkCONFORMS — verified. -
include missing.mkgraceful error (Critical #3 done);-includeimplemented with missing-file-ignore (Major #19 fixed).
-
LANG/LC_*—setlocale(LcAll, "")atmain.rs:165(CONFORMS for locale init; message coverage is #23). -
MAKEFLAGShonored (Major #13 fixed). -
SHELLmacro (not env var) used for recipe shell (Major #12 fixed). -
PROJECTDIR(XSI) — reviewed: an optional XSI SCCS search-path feature, intentionally out of scope alongside.SCCS_GETruntime retrieval. Documented as a known limitation, not a base-spec conformance failure.
- SIGHUP/SIGINT/SIGQUIT/SIGTERM handlers installed —
signal_handler.rs:40–43. - Non-precious in-progress target removed on signal —
signal_handler.rs:19–32. - Registration gated correctly (Major #17 fixed);
.PRECIOUSglobal honored (Major #18 fixed); reset-and-re-raise (Minor #20 fixed); mtime/.PHONYcleanup check (Minor #21 fixed).
- Recipe echo to stdout; diagnostics to stderr —
rule.rs,main.rs. - Recipe command failure → exit 2 — verified (
false→execution error: 1, exit 2).error_code.rs. - Up-to-date message —
main.rs:257–258. -
-kexit handling (Critical #4 fixed); granular internal codes conform (Minor #25, kept).
-
.DEFAULTCONFORMS — verified (fires for missing target).special_target.rs/lib.rs:136–143. Now also enforces the "specified with commands" requirement (Phase 9): an empty.DEFAULT:is a constraint violation. Testspecial_targets::validations::default_without_recipes. -
.PHONYCONFORMS — verified (forces rebuild twice).lib.rs:167–169. -
.SILENT(global) CONFORMS — verified.special_target.rs:259–267. -
.IGNOREPARTIAL — global form works; ordering caveat #22. -
.SCCS_GET(XSI) PARTIAL — recognized/stored; no runtime SCCS retrieval. Reviewed (Phase 9): full SCCS auto-retrieval is an optional XSI feature requiring SCCS tooling and is intentionally out of scope; the special target is parsed, validated (no prerequisites), and its command is stored/overridable. Documented as a known limitation rather than a conformance failure of the base spec. -
.POSIXaccepted (Critical #2 fixed, Phase 2). -
.SUFFIXESinsertion-ordered with clear/append (Major #16 fixed). -
.PRECIOUSglobal protection honored (Major #18 fixed). -
.WAIT/.NOTPARALLELrecognized and honored (Major #9 fixed). - Subsequent-occurrence accumulation (Minor #22 fixed) — sets now extend.
- One shell per recipe line CONFORMS —
rule.rs:209–219. -
@(silent) /-(ignore) /+(force) prefixes recognized —rule/recipe.rs;+forces under-n/-t(verified indirectly). - Shell
-ein effect (Major #11 fixed);+/$(MAKE)lines now run under-n/-t/-q($(MAKE)is passed through preprocessing, detected inrun_with_files, and expanded to the make program). Testsrecipe_execution::*. - Archive/library
lib(member.o)member mtime — PARTIAL→implemented for the timestamp:get_modified_timenow reads a member's stored mtime from theararchive (archive_member_mtime/parse_archive_target, unit-tested), so anarchive(member)string compares correctly. REMAINING (separate pre-existing parser gap, not the audited mtime issue): the rule parser cannot lex alib(member):target header ((is a distinct token), so such a rule cannot yet be declared in a makefile.
Existing tests are fixture-driven (make/tests/makefiles/**) and cover parsing of includes, recipe prefixes, and several special targets. Not covered (each is a "write a test" item):
- Recipe lines containing
=(#1) —recipe_line_with_equals(Phase 1). -
.POSIX:as the first line (#2) —special_targets::posix(Phase 2). - Missing
includefile → graceful error, and-include→ ignore (#3, #19) —preprocess::test_*include*. -
make -kexit status on success and on partial failure (#4) —arguments::dash_k_success+arguments::dash_k. - Command-line
macro=valueoperands and precedence (#5) —macros::cmdline_macro_*(Phase 2). -
$(VAR:.c=.o)substitution (#6) and backslash-newline continuation (#7) —preprocess::test_subst_*,test_continuation_*. - Single-suffix inference rules (#8) —
inference_rules::single_suffix_rule. -
-j,.WAIT,.NOTPARALLEL(#9) —parallel::*; multiple-f(#10). - Shell
-eabort on first failing command (#11) —recipe_execution::shell_e_aborts_on_first_failure;SHELLmacro vs env var (#12, behaviorally verified). -
MAKEFLAGSseeding options (#13);$?newer-only and$^/$+/$(@D)(#14, #15) —internal_macros::*,rule::tests::dir_and_file_parts. -
.SUFFIXESordering + clear/append (#16) —inference_rules::suffixes_clear_then_readd. - Signal-driven cleanup,
.PRECIOUSglobal, re-raise (#17, #18, #20, #21) —target_behavior::async_events*,special_targets::precious.
- PR A — "Don't choke on ordinary makefiles" (Critical #1, #3): make
generate_macro_tableskip recipe lines and validate the pre-=name; replace theincludeunwrap()with a diagnostic. Biggest correctness win. - PR B — "Portability basics" (Critical #2, #5; Major #10): accept
.POSIX; parse command-linemacro=valueoperands with correct precedence; accept multiple-f. - PR C — "
-k/exit-status correctness" (Critical #4; Minor #25): per-target error tracking; stop flagging success as failure. - PR D — "Macro completeness" (Major #6, #7, #13, #14, #15):
:subst=/%forms, backslash-newline folding,MAKEFLAGS,$?newer-only,$^/$+/$(@D)/$(@F). - PR E — "Inference & suffixes" (Major #8, #16; #19): single-suffix rules, insertion-ordered
.SUFFIXESwith clear/append, real-include. - PR F — "Recipe execution fidelity" (Major #11, #12): shell
-ewhen not ignoring; use theSHELLmacro, not the env var. - PR G — "Parallelism" (Major #9):
-j maxjobs+ token pool +.WAIT/.NOTPARALLEL(larger, can be staged last). - PR H — "Signals" (Major #17, #18; Minor #20, #21): correct registration gating,
.PRECIOUSglobal flag, reset-and-re-raise, mtime/.PHONYchecks.
Per the playbook, claims that did not survive verification are recorded, not deleted:
$(VAR)in recipes reaches the shell verbatim — REFUTED.CC=echoexpands toecho …and runs; user macros are expanded at preprocess time.$$passes through as$$— REFUTED.$$collapses to$(verifiedecho price is $$5→price is).?=logic is inverted — REFUTED. Keeps an existing value and falls back when unset (both verified).-includeof a missing file panics — REFUTED. It does not panic (the line is passed through; the real gap is that-includeis not implemented, #19). The non-prefixedincludeis what panics (#3).