diff --git a/scripts/adjust_docx_whitespace.php b/scripts/adjust_docx_whitespace.php
deleted file mode 100644
index 12c1ecb..0000000
--- a/scripts/adjust_docx_whitespace.php
+++ /dev/null
@@ -1,143 +0,0 @@
-\n");
- exit(2);
-}
-
-$path = $argv[1];
-$wordNs = 'http://schemas.openxmlformats.org/wordprocessingml/2006/main';
-$zip = new ZipArchive();
-if ($zip->open($path) !== true) {
- throw new RuntimeException("Cannot open DOCX: {$path}");
-}
-
-$documentXml = $zip->getFromName('word/document.xml');
-if ($documentXml === false) {
- throw new RuntimeException('word/document.xml is missing');
-}
-
-$doc = new DOMDocument('1.0', 'UTF-8');
-$doc->preserveWhiteSpace = false;
-$doc->formatOutput = false;
-if (!$doc->loadXML($documentXml)) {
- throw new RuntimeException('word/document.xml is invalid');
-}
-
-$xpath = new DOMXPath($doc);
-$xpath->registerNamespace('w', $wordNs);
-
-function attr(DOMElement $element, string $name, string $value, string $ns): void
-{
- $element->setAttributeNS($ns, 'w:' . $name, $value);
-}
-
-function child(DOMDocument $doc, DOMElement $parent, string $localName, string $ns): DOMElement
-{
- foreach ($parent->childNodes as $node) {
- if ($node instanceof DOMElement && $node->namespaceURI === $ns && $node->localName === $localName) {
- return $node;
- }
- }
- $element = $doc->createElementNS($ns, 'w:' . $localName);
- $parent->appendChild($element);
- return $element;
-}
-
-foreach ($xpath->query('//w:body/w:p | //w:body/w:tbl//w:p') as $paragraph) {
- if (!$paragraph instanceof DOMElement) {
- continue;
- }
-
- $text = '';
- foreach ($xpath->query('.//w:t', $paragraph) as $textNode) {
- $text .= $textNode->textContent;
- }
- $text = trim(preg_replace('/\s+/u', ' ', $text) ?? '');
-
- $pPrNode = $xpath->query('./w:pPr', $paragraph)->item(0);
- if (!$pPrNode instanceof DOMElement) {
- $pPrNode = $doc->createElementNS($wordNs, 'w:pPr');
- $paragraph->insertBefore($pPrNode, $paragraph->firstChild);
- }
- $spacing = child($doc, $pPrNode, 'spacing', $wordNs);
-
- $before = '0';
- $after = '30';
- $line = '320';
-
- if ($text === '') {
- $after = '20';
- $line = '120';
- } elseif (preg_match('/^[1-7]\.\s/u', $text)) {
- $before = '120';
- $after = '45';
- $line = '330';
- } elseif (preg_match('/^[1-7]\.\d\s/u', $text)) {
- $before = '70';
- $after = '30';
- $line = '320';
- } elseif (str_starts_with($text, '•')) {
- $after = '15';
- $line = '310';
- } elseif (str_starts_with($text, 'วันที่ 5 มกราคม')) {
- $before = '40';
- $after = '100';
- $line = '330';
- } elseif (str_starts_with($text, 'เรื่อง') || str_starts_with($text, 'เรียน')) {
- $after = '55';
- $line = '330';
- } elseif (str_starts_with($text, 'ลงชื่อ')) {
- $before = '160';
- $after = '10';
- $line = '310';
- } elseif (str_starts_with($text, '(') || str_starts_with($text, 'ตำแหน่ง') || str_starts_with($text, 'วันที่ _') || str_starts_with($text, 'ผู้มีอำนาจ')) {
- $after = '10';
- $line = '310';
- } elseif (str_starts_with($text, 'บริษัท บี.อาร์.เอ็น.') || str_starts_with($text, 'เอกสารฉบับนี้') || str_starts_with($text, 'ผลส่งมอบประกอบ') || str_starts_with($text, 'การเปลี่ยนแปลง') || str_starts_with($text, 'จึงจัดทำ')) {
- $after = '55';
- $line = '330';
- }
-
- attr($spacing, 'before', $before, $wordNs);
- attr($spacing, 'after', $after, $wordNs);
- attr($spacing, 'line', $line, $wordNs);
- attr($spacing, 'lineRule', 'auto', $wordNs);
-}
-
-foreach ($xpath->query('//w:sectPr/w:pgMar') as $margin) {
- if (!$margin instanceof DOMElement) {
- continue;
- }
- attr($margin, 'top', '850', $wordNs);
- attr($margin, 'bottom', '850', $wordNs);
- attr($margin, 'left', '1080', $wordNs);
- attr($margin, 'right', '1080', $wordNs);
- attr($margin, 'header', '400', $wordNs);
- attr($margin, 'footer', '400', $wordNs);
-}
-
-foreach ($xpath->query('//w:tbl/w:tblPr') as $tableProperties) {
- if (!$tableProperties instanceof DOMElement) {
- continue;
- }
- $margins = child($doc, $tableProperties, 'tblCellMar', $wordNs);
- foreach (['top' => '45', 'left' => '70', 'bottom' => '45', 'right' => '70'] as $side => $width) {
- $edge = child($doc, $margins, $side, $wordNs);
- attr($edge, 'w', $width, $wordNs);
- attr($edge, 'type', 'dxa', $wordNs);
- }
-}
-
-$updatedXml = $doc->saveXML();
-if ($updatedXml === false) {
- throw new RuntimeException('Cannot serialize adjusted document XML');
-}
-if (!$zip->addFromString('word/document.xml', $updatedXml)) {
- throw new RuntimeException('Cannot update word/document.xml');
-}
-$zip->close();
-
-echo $path . PHP_EOL;
diff --git a/scripts/build-sdlc-delivery.sh b/scripts/build-sdlc-delivery.sh
index b1ad39d..96bc370 100755
--- a/scripts/build-sdlc-delivery.sh
+++ b/scripts/build-sdlc-delivery.sh
@@ -8,6 +8,11 @@ SOURCE_DIR="$ROOT_DIR/sdlc"
DELIVERY_ROOT="$ROOT_DIR/sdlc-delivery"
STAGING_DIR="$ROOT_DIR/.sdlc-delivery.staging"
+# sdlc/ is generated output: the seed rebuilds every work product from the
+# controlled project data in scripts/sdlc-seed/ before anything is rendered.
+echo "Seeding work products from project data..."
+node "$SCRIPT_DIR/sdlc-seed/build.mjs"
+
if [[ ! -d "$TOOL_DIR/node_modules/playwright" ]]; then
echo "Installing the local PDF renderer..."
npm install --prefix "$TOOL_DIR"
diff --git a/scripts/format_progress_status_html.php b/scripts/format_progress_status_html.php
deleted file mode 100644
index bbd103a..0000000
--- a/scripts/format_progress_status_html.php
+++ /dev/null
@@ -1,117 +0,0 @@
-)~s', $template, $match)) {
- fwrite(STDERR, "The template does not contain the approved Progress Status Record CSS.\n");
- exit(1);
-}
-
-// The CSS must work for every record number, not just record 1.
-$layoutCss = str_replace(
- '#progress-status-record-1-of-13',
- 'h1[id^="progress-status-record-"]',
- $match[1]
-);
-
-/** @return string */
-function approvalCard(string $id, string $title, string $paragraph): string
-{
- $lines = preg_split('~
\s*~i', trim($paragraph)) ?: [];
- $items = [];
- foreach ($lines as $line) {
- $line = trim($line);
- if ($line === '') {
- continue;
- }
- if (preg_match('~^(Signature|Date):\s*_*\s*$~i', strip_tags($line), $label)) {
- $items[] = '
' . htmlspecialchars($label[1], ENT_QUOTES, 'UTF-8') . ':
'; - } else { - $items[] = '' . $line . '
'; - } - } - return '